Есть ли что-нибудь, чего не может достичь композиция, которую может наследовать? - PullRequest
24 голосов
/ 10 февраля 2010

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

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

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

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

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

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

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

Редактировать

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

Ответы [ 5 ]

23 голосов
/ 10 февраля 2010

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

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

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

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. Мы достигаем того же, что и с наследованием. Этот механизм используется в основанном на прототипах языке , таком как 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() ; ... }
}

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

Обсуждение

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

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

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

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

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

Наследование и делегирование альтернативные методы для приращения определение и обмен. Она имеет Считается, что делегация обеспечивает более мощную модель. это бумага демонстрирует, что есть «Естественная» модель наследования, которая захватывает все свойства делегация. Независимо, наверняка ограничения на способность Делегация для захвата наследства продемонстрировал. Наконец, новая структура который полностью захватывает обе делегации и наследование намечено, а некоторые Последствия этого гибрида Модель исследована.

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

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

3 голосов
/ 10 февраля 2010

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

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

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

1 голос
/ 03 марта 2013

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

Предположим, у меня есть библиотека виджетов с закрытым исходным кодом, которую я использую в проекте (то есть детали реализации для меня загадка, помимо того, что задокументировано). Теперь предположим, что каждый виджет имеет возможность добавлять дочерние виджеты. С наследованием я мог бы расширить класс 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);

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

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

1 голос
/ 10 февраля 2010

Рассмотрим графический интерфейс.

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

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

0 голосов
/ 11 апреля 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...