Есть ли что-нибудь, что композиция не может выполнить это наследование?

Состав и наследование.

Я знаю, что они оба являются инструментами для выбора, когда это необходимо, и контекст очень важен при выборе между композицией и наследованием. Однако обсуждение подходящего контекста для каждого из них обычно немного нечеткое; это приводит к тому, что я начинаю рассматривать, насколько отчетливо наследование и полиморфизм являются отдельными аспектами традиционного ООП.

Полиморфизм позволяет указывать отношения "is-a" одинаково, а также наследование. В частности, наследование из базового класса неявно создает полиморфную связь между этим классом и его подклассами. Однако, в то время как полиморфизм может быть реализован с использованием чистых интерфейсов, наследование усложняет полиморфную взаимосвязь, одновременно передавая детали реализации. Таким образом, наследование отличается от чистого полиморфизма.

В качестве инструмента наследование обслуживает программистов иначе, чем полиморфизм (через чистые интерфейсы), упрощая повторное использование реализации в тривиальных случаях. Однако в большинстве случаев детали реализации суперкласса тонко противоречат требованиям подкласса. Вот почему у нас есть "переопределения" и "сокрытие членов". В этих случаях повторное использование реализации, предлагаемое путем наследования, приобретается с добавлением усилий по проверке изменений состояния и путей выполнения на каскадных уровнях кода: полная "сглаженная" информация о реализации подкласса распространяется между несколькими классами, что обычно означает несколько файлов, из которых только отдельные части относятся к рассматриваемому подклассу. Просматривая эту иерархию, абсолютно необходимо иметь дело с наследованием, потому что, не глядя на код суперкласса, нет способа узнать, какие неперекрываемые детали обезьяны с вашим состоянием или отвлечение вашего исполнения.

Для сравнения, исключительное использование композиции гарантирует, что вы увидите, какое состояние может быть изменено с помощью явно созданных объектов, методы которых вызывается по вашему усмотрению. Поистине сглаженная реализация все еще не достигнута (и на самом деле это даже не желательно, поскольку преимущество структурированного программирования - это инкапсуляция и абстракция деталей реализации), но вы все равно получаете повторное использование кода, и вам нужно будет только посмотреть в одном месте когда код неверно работает.

С целью тестирования этих идей на практике, избегая традиционного наследования для комбинации чистого полиморфизма на основе интерфейса и состава объекта, мне интересно,

Есть ли что-нибудь, что состав объекта и интерфейсы не могут выполнить это наследование?

Edit

В ответах до сих пор, ewernli полагает, что нет технических умений, доступных для одного метода, но не другого; он позже упоминает, как разные шаблоны и подходы к дизайну присущи каждой технике. Это разумно. Тем не менее, это побуждает меня уточнить мой вопрос, спросив, запретит ли эксклюзивное использование композиции и интерфейсов вместо традиционного наследования использовать любые основные шаблоны проектирования? И если да, разве у вас нет эквивалентных шаблонов для использования в моей ситуации?

Ответ 1

Технически все, что можно реализовать с наследованием, может быть реализовано и с делегированием. Таким образом, ответ будет "нет".

Преобразование наследования в делецию

Скажем, у нас есть следующие классы, реализованные с наследованием:

public class A {
    String a = "A";
    void doSomething() { .... }
    void getDisplayName() {  return a }
    void printName { System.out.println( this.getDisplayName() };   
}

public class B extends A {
    String b = "B";
    void getDisplayName() {  return a + " " + b; }
    void doSomething() { super.doSomething() ; ... }    
}

Материал работает красиво, а вызов printName в экземпляре B будет печатать "A B" в консоли.

Теперь, если мы перепишем это с делегированием, получим:

public class A {
    String a = "A";
    void doSomething() { .... }
    void getDisplayName() {  return a }
    void printName { System.out.println( this.getName() };  
}

public class B  {
    String b = "B";
    A delegate = new A();
    void getDisplayName() {  return delegate.a + " " + b; }
    void doSomething() { delegate.doSomething() ; ... } 
    void printName() { delegate.printName() ; ... }
}

Нам нужно определить printName в B, а также создать делегат при создании экземпляра B. Вызов doSomething будет работать так же, как и с наследованием. Но вызов printName будет печатать "A" в консоли. Действительно, с делегацией мы потеряли мощную концепцию "this", связанную с экземпляром объекта и базовыми методами, способными вызывать методы, которые были отменены.

Это можно решить на языке, поддерживающем чистое делегирование. При чистое делегирование "this" в делегате по-прежнему будет ссылаться на экземпляр B. Это означает, что this.getName() начнет отправку метода из класса B. Мы достигнем того же, что и с наследованием. Это механизм, используемый в prototype-based, например Self, у которых есть делегирование, имеет встроенную функцию (вы можете прочитать здесь, как наследование работает в Self).

Но Java не имеет чистого делегирования. Когда они застряли? Нет, мы все еще можем сделать это с еще большим усилием:

public class A implements AInterface {
    String a = "A";
    AInterface owner; // replace "this"
    A ( AInterface o ) { owner = o }
    void doSomething() { .... }
    void getDisplayName() {  return a }
    void printName { System.out.println( owner.getName() }; 
}

public class B  implements AInterface {
    String b = "B";
    A delegate = new A( this );
    void getDisplayName() {  return delegate.a + " " + b; }
    void doSomething() { delegate.doSomething() ; ... } 
    void printName() { delegate.printName() ; ... }
}

Мы в основном реинжинируем то, что обеспечивает встроенное наследование. Имеет ли это смысл? Нет. Но это иллюстрирует, что наследование всегда может быть преобразовано в делецию.

Обсуждение

Наследование характеризуется тем, что базовый класс может вызывать метод, который переопределяется в подклассе. Это, например, суть шаблона шаблона . С делегацией такие вещи нелегко сделать. С другой стороны, это именно то, что делает наследование трудным в использовании. Для этого требуется ментальный поворот, чтобы понять, где происходит полиморфная отправка, и каков эффект, если методы переопределены.

Есть некоторые известные подводные камни о наследовании и хрупкости, которые он может внести в дизайн. Особенно, если иерархия классов развивается. Также могут возникнуть проблемы с equality в hashCode и equals, если используется наследование. Но с другой стороны, это еще очень элегантный способ решения некоторых проблем.

Кроме того, даже если наследование может быть заменено делегированием, можно утверждать, что они все еще достигают различной цели и дополняют друг друга - они не передают одно и то же намерение, которое не захватывается чистой технической эквивалентностью.

(Моя теория заключается в том, что, когда кто-то начинает делать OO, у нас возникает соблазн чрезмерно использовать наследование, потому что оно воспринимается как особенность языка. Затем мы изучаем делегирование, которое является шаблоном/подходом, и мы также учимся любить его. Через некоторое время мы находим баланс между обоими и развиваем чувство интуиции, из которого лучше в этом случае. Ну, как вы можете видеть, мне все еще нравятся оба, и оба заслуживают некоторой осторожности перед тем, как их ввести.)

Некоторая литература

Наследование и делегирование альтернативные методы для инкрементного определения и совместного использования. В нем есть обычно считалось, что делегация обеспечивает более мощную модель. Эта бумага показывает, что существует "естественная" модель наследования, которая захватывает все свойства делегация. Независимо определенные ограничения на способность делегирование для наследования продемонстрировал. Наконец, новая структура который полностью отражает обе делегации и наследование очерчено, а некоторые последствий этого гибрида модель.

Один из самых интригующих - и на в то же время большинство проблемных понятий в объектно-ориентированное программирование наследование. Наследование обычно рассматривается как функция, которая отличает объектно-ориентированную программирование из других современных парадигмы программирования, но исследователи редко соглашаются на его значение и использование. [...]

Из-за сильной связи классов и распространения ненужных членов класса, индуцированных по наследованию, предложение использовать состав и делегирование вместо этого стало обычным явлением. Презентация соответствующего рефакторинга в литературе может привести к тому, что такое преобразование является прямым делом. [...]

Ответ 2

Состав не может повредить жизнь, как и наследование, когда быстрые программисты пули пытаются решить проблемы, добавляя методы и расширяя иерархии (вместо того, чтобы думать о естественных иерархиях)

Состав не может привести к появлению странных алмазов, которые заставляют группы обслуживания сжигать ночное масло, царапая головы.

Наследование было сущностью обсуждения в шаблонах проектирования GOF, которые не были бы одинаковыми, если бы программисты использовали композицию в первую очередь.

Ответ 3

Рассмотрим набор инструментов gui.

Элемент управления редактированием - это окно, оно должно наследовать функции закрытия окна window/enable/paint - оно не содержит окна.

Затем расширенный текстовый элемент управления должен содержать функции управления сохранением/чтением/вырезанием/вставкой для редактирования, это было бы очень сложно использовать, если бы оно содержало только окно и элемент управления редактирования.

Ответ 4

Возможно, я ошибаюсь, но я все равно скажу, и если у кого есть причина, я ошибаюсь, просто ответьте комментарием и не пропустите меня. Есть одна ситуация, я могу думать, где наследство будет превосходить состав.

Предположим, что у меня есть библиотека Widget с закрытым исходным кодом, которую я использую в проекте (это означает, что детали реализации являются для меня загадкой, кроме того, что документировано). Теперь предположим, что у каждого виджета есть возможность добавлять дочерние виджеты. С наследованием я мог бы расширить класс Widget для создания CustomWidget, а затем добавить CustomWidget как дочерний виджет любого другого виджета в библиотеке. Тогда мой код для добавления CustomWidget будет выглядеть примерно так:

Widget baseWidget = new Widget();
CustomWidget customWidget = new CustomWidget();
baseWidget.addChildWidget(customWidget);

Очень чистая и поддерживает правила библиотеки для добавления дочерних виджетов. Однако с составом это должно быть примерно так:

Widget baseWidget = new Widget();
CustomWidget customWidget = new CustomWidget();
customWidget.addAsChildToWidget(baseWidget);

Не так чисто, а также нарушает соглашения библиотеки

Теперь я не говорю, что вы не смогли бы выполнить это с композицией (на самом деле мой пример показывает, что вы очень четко можете), он просто не идеален при любых обстоятельствах и может привести к нарушению условностей и другим довольно визуально непривлекательным обходные пути.

Ответ 5

Да. Это идентификация типа времени выполнения (RTTI).