Я задавал подобный вопрос месяц назад: Наследование через состав?
Я взял примеры из этой статьи: http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html?page=1
Однако на этот раз это другая проблема. Извините за публикацию большого количества кодов, но это просто долго, это не трудно читать.
class Fruit {
public int peel() {
System.out.println("Peeling is appealing.");
return 1;
}
}
class Apple extends Fruit {
}
class Example1 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();
}
}
После модификации это уже не нормально:
class Peel {
private int peelCount;
public Peel(int peelCount) {
this.peelCount = peelCount;
}
public int getPeelCount() {
return peelCount;
}
}
Модификация класса Fruit приводит к коду ошибки компиляции.
class Peel {
private int peelCount;
public Peel(int peelCount) {
this.peelCount = peelCount;
}
public int getPeelCount() {
return peelCount;
}
}
class Fruit {
// Return a Peel object that
// results from the peeling activity.
public Peel peel() {
System.out.println("Peeling is appealing.");
return new Peel(1);
}
}
Старая реализация не работает. Эту проблему можно решить по составу:
class Peel {
private int peelCount;
public Peel(int peelCount) {
this.peelCount = peelCount;
}
public int getPeelCount() {
return peelCount;
}
}
class Fruit {
// Return int number of pieces of peel that
// resulted from the peeling activity.
public Peel peel() {
System.out.println("Peeling is appealing.");
return new Peel(1);
}
}
// Apple must be changed to accomodate
// the change to Fruit
class Apple {
private Fruit fruit = new Fruit();
public int peel() {
Peel peel = fruit.peel();
return peel.getPeelCount();
}
}
// This old implementation of Example2
// still works fine.
class Example1 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();
}
}
Со стилем композиции яблоко больше не является - это отношения с Fruit. Это стало имением. Методы из Fruit делегируются Apple как наследство. Здесь возникают мои вопросы:
С методами-обертками для делегирования методов из Fruit, разве это не продвигает копирование и вставку, что является плохой практикой? Изображение, если у нас есть около 10-50 методов во фруктах. Как можно наследовать по составу, если существует около 50 подклассов для наследования?
Что касается первого примера наследования с использованием расширения, автор предполагает, что одно изменение в суперклассе нарушит реализации, использующие его. Однако, мне интересно, почему это нужно изменить? Разве один из принципов ОО «закрыт для модификации, открыт для расширения»? В случае заявленного автора, я все еще могу сохранить старую реализацию суперкласса и все же заставить ее адаптироваться к новым изменениям. Как это:
класс Фрукты {
// Return a Peel object that
// results from the peeling activity.
private Peel getPeel() {
System.out.println("Peeling is appealing.");
return new Peel(1);
}
public int peel(){
Peel peel = getPeel();
return peel.getPeelCount();
}
}
Что-то не так с добавлением новых методов в суперкласс для адаптации вместо изменения или замены старых методов, что нарушит структуру программы? Насколько я вижу, делая это, я все еще могу достичь того же, что и в примере композиции, а также мне не нужно будет создавать множество методов-оболочек для делегирования методов суперкласса для каждого подкласса.
- По вышеуказанной причине я заключаю себя в том, что единственное время, когда используется композиция, лучше, когда существует несколько наследований или когда мне нужно поделиться поведением, которое варьируется и не относится к одной классовой семье. Например:
интерфейс Talkative {
public void talk();
}
Семейство классов животных может реализовывать интерфейс Talkative, а также семейство классов Person. Что вы думаете о моем заключении?