Ця стаття ілюструє концепцію спадкування та композиції в Java. Перший приклад демонструє успадкування, а потім показує, як покращити дизайн успадкування з використанням композиції. Про те, як вибрати між ними, ми підсумуємо в кінці.
1. Спадкування
Давайте припустимо, що у нас є класInsect
(англ. комаха) Цей клас містить два методи: 1. move()
(з англ. пересуватися) і 2. attack()
(з англ. атакувати)
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");
}
}
Тепер ви хочете визначити клас Bee
(англ бджола), який є одним із видів Insect
, але має різні реалізації attack()
and move()
. Це може бути оформлено за допомогою успадкування:
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();
}
}
Діаграма ієрархії класів досить проста: Результат виконання:
Fly
Fly
Attack
"Fly" надруковано двічі, отже метод move()
викликається двічі. Але він має викликатися лише один раз. Проблема викликана методом super.attack()
. Метод attack ()
викликає метод move()
класу Insect
. Коли підклас викликає super.attack ()
, він також викликає перевизначений метод move()
. Щоб виправити проблему ми можемо:
- Усунути метод
attack()
підкласу. Це зробить підклас, залежним від реалізації методуattack()
суперкласу. Якщоattack()attack()
суперклас почне використовувати інший метод для переміщення, підклас потрібно буде також змінити. Це погано інкапсуляція. -
Переписати метод
attack()
так:public void attack() { move(); System.out.println("Attack"); }
-
Це гарантує правильний результат, тому що підклас більше не залежить від суперкласу. Однак код є дублікатом суперкласу. (Метод
attack()
робить більш складні речі, ніж просто виведення рядка). Це не відповідає правильному конструюванню програмного забезпечення, коду, що повторюється, не повинно бути.
2. Композиція
Замість успадкування можна використовувати композицію. Погляньмо на рішення з її допомогою. Функціяattack()
абстрагується як інтерфейс.
interface Attack {
public void move();
public void attack();
}
Різні види атаки можна визначити шляхом реалізації інтерфейсу Attack.
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);
}
}
Оскільки функція атаки зовнішня, Insect
її клас більше не містить.
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;
}
}
Клас Bee
(з англ Бджола) як тип Insect
може атакувати.
// 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();
}
}
Діаграма класів:
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();
}
}
Результат виконання:
fly
move
fly
sting
3. Коли використовувати ці підходи?
Наступні 2 пункти можуть допомогти вибрати між успадкуванням та композицією:- якщо маєте справу з ставленням між класами виду "Є" і клас хоче надати всі свої інтерфейси іншому класу, то успадкування краще.
- якщо відношення "МАЄ", то краще композиція.
- Bloch, Joshua. Ефективний 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--which-one-should...
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ