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.
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: Wynik 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:
- Wyeliminuj
attack()
metodę podklasy. Spowoduje to uzależnienie podklasy odattack()
implementacji metody nadklasy. Jeśliattack()attack()
nadklasa zacznie używać innej metody przenoszenia, podklasa również będzie musiała się zmienić. To zła enkapsulacja. -
Przepisz metodę
attack()
w następujący sposób:public void attack() { move(); System.out.println("Attack"); }
-
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.
2. Skład
Zamiast dziedziczenia możesz użyć kompozycji. Przyjrzyjmy się rozwiązaniu z jego wykorzystaniem. Funkcjaattack()
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 Insect
już 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 Insect
moż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:
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ą:- 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.
- jeśli relacja ma wartość „HAS”, preferowana jest kompozycja.
- Bloch, Jozue. Efektywna Java. Pearson Education India, 2008
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--what-one-should...
GO TO FULL VERSION