JavaRush /Blog Java /Random-PL /Dziedziczenie a kompozycja w Javie
dio
Poziom 16
Москва

Dziedziczenie a kompozycja w Javie

Opublikowano w grupie Random-PL
W tym artykule przedstawiono koncepcje dziedziczenia i kompozycji w Javie. Pierwszy przykład demonstruje dziedziczenie, a następnie pokazuje, jak ulepszyć projekt dziedziczenia za pomocą kompozycji. Na koniec podsumujemy, jak dokonać wyboru między nimi. Dziedziczenie a kompozycja w Javie — 1

1. Dziedziczenie

Załóżmy, że mamy klasę Insect(angielski owad). Klasa ta zawiera dwie metody: 1. move()(z angielskiego ruchu) i 2. attack()(z angielskiego ataku)
class Insect {
	private int size;
	private String color;

	public Insect(int size, String color) {
		this.size = size;
		this.color = color;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public void move() {
		System.out.println("Move");
	}

	public void attack() {
		move(); //assuming an insect needs to move before attacking
		System.out.println("Attack");
	}
}
Teraz chcesz zdefiniować klasę Bee(angielski bee), która jest jednym z typów Insect, ale ma różne implementacje attack()i move(). Można to zrobić za pomocą dziedziczenia:
class Bee extends Insect {
	public Bee(int size, String color) {
		super(size, color);
	}

	public void move() {
		System.out.println("Fly");
	}

	public void attack() {
		move();
		super.attack();
	}
}
public class InheritanceVSComposition {
	public static void main(String[] args) {
		Insect i = new Bee(1, "red");
		i.attack();
	}
}
Diagram hierarchii klas jest dość prosty: Dziedziczenie a kompozycja w Javie - 2Wynik wykonania:
Fly
Fly
Attack
„Fly” jest wpisywane dwukrotnie, więc metoda move()jest wywoływana dwukrotnie. Ale należy to wywołać tylko raz. Problem jest spowodowany przez super.attack(). Metoda attack ()wywołuje metodę move()klasową Insect. Kiedy podklasa wywołuje super.attack (), wywołuje także przesłoniętą metodę move(). Aby rozwiązać problem, możemy:
  1. Wyeliminuj attack()metodę podklasy. Spowoduje to uzależnienie podklasy od attack()implementacji metody nadklasy. Jeśli attack()attack()nadklasa zacznie używać innej metody przenoszenia, podklasa również będzie musiała się zmienić. To zła enkapsulacja.
  2. Przepisz metodę attack()w następujący sposób:

    public void attack() {
    	move();
    	System.out.println("Attack");
    }
  3. Gwarantuje to poprawny wynik, ponieważ podklasa nie jest już zależna od nadklasy. Jednakże kod jest duplikatem nadklasy. (metoda attack()robi bardziej złożone rzeczy niż tylko wysyłanie ciągu znaków). To nie jest dobry projekt oprogramowania i nie powinno być duplikatu kodu.

Ten projekt dziedziczenia jest zły, ponieważ podklasa zależy od szczegółów implementacji swojej nadklasy. Jeśli nadklasa ulegnie zmianie, podklasa nie będzie działać poprawnie.

2. Skład

Zamiast dziedziczenia możesz użyć kompozycji. Przyjrzyjmy się rozwiązaniu z jego wykorzystaniem. Funkcja attack()jest abstrahowana jako interfejs.
interface Attack {
	public void move();
	public void attack();
}
Implementując interfejs Attack, można zdefiniować różne typy ataków.
class AttackImpl implements Attack {
	private String move;
	private String attack;

	public AttackImpl(String move, String attack) {
		this.move = move;
		this.attack = attack;
	}

	@Override
	public void move() {
		System.out.println(move);
	}

	@Override
	public void attack() {
		move();
		System.out.println(attack);
	}
}
Ponieważ funkcja ataku jest zewnętrzna, klasa Insectjuż jej nie zawiera.
class Insect {
	private int size;
	private String color;

	public Insect(int size, String color) {
		this.size = size;
		this.color = color;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}
}
Klasa Bee(od angielskiego Bee), w jaki sposób typ Insectmoże atakować.
// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
	private Attack attack;

	public Bee(int size, String color, Attack attack) {
		super(size, color);
		this.attack = attack;
	}

	public void move() {
		attack.move();
	}

	public void attack() {
		attack.attack();
	}
}
Schemat klas: Dziedziczenie a kompozycja w Javie — 3
public class InheritanceVSComposition2 {
	public static void main(String[] args) {
		Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
		a.attack();

		// if you need another implementation of move()
		// there is no need to change Insect, we can quickly use new method to attack

		Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
		b.attack();
	}
}
Wynik wykonania:
fly
move
fly
sting

3. Kiedy stosować te podejścia?

Poniższe 2 punkty mogą pomóc w podjęciu decyzji pomiędzy dziedziczeniem a kompozycją:
  1. Jeśli masz do czynienia z relacją pomiędzy klasami w postaci „IS”, a klasa chce udostępnić wszystkie swoje interfejsy innej klasie, wówczas preferowane jest dziedziczenie.
  2. jeśli relacja ma wartość „HAS”, preferowana jest kompozycja.
Zatem dziedziczenie i kompozycja mają swoje zastosowania i warto poznać ich zalety. Spinki do mankietów:
  1. Bloch, Jozue. Efektywna Java. Pearson Education India, 2008
  2. https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
  3. https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--what-one-should...
Link do oryginalnego artykułu Przetłumaczone
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION