Использование для множественного наследования? - PullRequest
19 голосов
/ 22 февраля 2009

Может ли кто-нибудь придумать, в какой ситуации можно использовать множественное наследование? Каждый случай, который я могу придумать, может быть решен оператором метода

AnotherClass() { return this->something.anotherClass; }

Ответы [ 12 ]

27 голосов
/ 22 февраля 2009

В большинстве случаев полнофункциональное множественное наследование предназначено для миксинов. Как пример:

class DraggableWindow : Window, Draggable { }
class SkinnableWindow : Window, Skinnable { }
class DraggableSkinnableWindow : Window, Draggable, Skinnable { }

и т.д ...

В большинстве случаев лучше использовать множественное наследование для строгого наследования интерфейса.

class DraggableWindow : Window, IDraggable { }

Затем вы реализуете интерфейс IDraggable в своем классе DraggableWindow. Слишком сложно писать хорошие классы миксина.

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

Например, во многих каркасах классов вы видите что-то вроде этого:

class Control { }
class Window : Control { }
class Textbox : Control { }

Теперь, предположим, вы хотели текстовое поле с характеристиками окна? Как быть перетаскиваемым, иметь заголовок и т. Д. Вы могли бы сделать что-то вроде этого:

class WindowedTextbox : Control, IWindow, ITexbox { }

В модели с единым наследованием вы не можете легко наследовать как из Window, так и из Textbox, не имея проблем с дублирующимися объектами Control и других проблем. Вы также можете рассматривать WindowedTextbox как окно, текстовое поле или элемент управления.

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

12 голосов
/ 22 февраля 2009

Я считаю, что множественное наследование особенно полезно при использовании mixin классов.

Как сказано в Википедии:

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

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

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

Пример множественного наследования:

class Animal
{
   virtual void KeepCool() const = 0;
}

class Vertebrate
{
   virtual void BendSpine() { };
}


class Dog : public Animal, public Vertebrate
{
   void KeepCool() { Pant(); }
}

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

Приведенный выше пример хорошо структурирован, потому что собака является животным, а также позвоночным животным.

4 голосов
/ 22 февраля 2009

Java имеет интерфейсы. C ++ не имеет.

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

4 голосов
/ 22 февраля 2009

Большинство людей используют множественное наследование в контексте применения нескольких интерфейсов к классу. Это подход Java и C #, среди прочего, принудительный.

C ++ позволяет применять несколько базовых классов довольно свободно, в зависимости между типами. Таким образом, вы можете обрабатывать производный объект как любой из его базовых классов.

Другое использование, как LeopardSkinPillBoxHat указывает на , находится в смешанных модулях. Прекрасным примером этого является библиотека Loki из книги Андрея Александреску «Современный дизайн C ++». Он использует то, что он называет классами политики , которые определяют поведение или требования данного класса посредством наследования.

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

Использование для МИ много. Потенциал для злоупотребления еще больше.

3 голосов
/ 22 февраля 2009

Я думаю, что это было бы наиболее полезно для стандартного кода. Например, шаблон IDisposable одинаков для всех классов в .NET. Так зачем вводить этот код снова и снова?

Другой пример - ICollection. Подавляющее большинство методов интерфейса реализованы точно так же. Есть только пара методов, которые на самом деле уникальны для вашего класса.

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

2 голосов
/ 22 февраля 2009

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

MI (реализации или интерфейса) может использоваться для добавления функциональности. Их часто называют смешанными классами. Представьте, что у вас есть графический интерфейс. Существует класс представления, который обрабатывает рисование, и класс Drag & Drop, который обрабатывает перетаскивание. Если у вас есть объект, который выполняет обе функции, у вас был бы класс, подобный

class DropTarget{
 public void Drop(DropItem & itemBeingDropped);
...
}

class View{
  public void Draw();
...
}

/* View you can drop items on */
class DropView:View,DropTarget{

}
2 голосов
/ 22 февраля 2009

В одном случае я работал с недавно задействованными сетевыми принтерами этикеток. Нам нужно печатать этикетки, поэтому у нас есть класс LabelPrinter. В этом классе есть виртуальные вызовы для печати нескольких разных этикеток. У меня также есть универсальный класс для вещей, связанных с TCP / IP, которые могут подключаться, отправлять и получать. Поэтому, когда мне нужно было реализовать принтер, он унаследовал от класса LabelPrinter и класса TcpIpConnector.

1 голос
/ 23 февраля 2009

Я подозреваю, что в C ++ MI лучше всего использовать как часть фреймворка (классы mix-in, обсуждавшиеся ранее). Единственное, что я точно знаю, это то, что каждый раз, когда я пытался использовать его в своих приложениях, я в конечном итоге сожалел о своем выборе, часто выбрасывал его и заменял сгенерированным кодом.

MI - еще один из тех, кто «использует его, если он ДЕЙСТВИТЕЛЬНО ему нужен, но убедитесь, что он ДЕЙСТВИТЕЛЬНО нужен».

1 голос
/ 23 февраля 2009

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

Этот пример на самом деле не иллюстрирует полезность множественного наследования. То, что здесь определяется, это ИНТЕРФЕЙС. Множественное наследование также позволяет наследовать поведение. Какой смысл в миксинах.

Пример; из-за необходимости сохранения обратной совместимости я должен реализовать свои собственные методы сериализации.

Таким образом, каждый объект получает метод Read and Store, подобный этому.

Public Sub Store(ByVal File As IBinaryWriter)
Public Sub Read(ByVal File As IBinaryReader)

Я также хочу иметь возможность назначать и клонировать объект. Так что я хотел бы это на каждом объекте.

Public Sub Assign(ByVal tObject As <Class_Name>)
Public Function Clone() As <Class_Name>

Теперь в VB6 этот код повторяется снова и снова.

Public Assign(ByVal tObject As ObjectClass)
    Me.State = tObject.State
End Sub

Public Function Clone() As ObjectClass
    Dim O As ObjectClass
    Set O = New ObjectClass
    O.State = Me.State
    Set Clone = 0
End Function

Public Property Get State() As Variant
    StateManager.Clear
    Me.Store StateManager
    State = StateManager.Data
End Property

Public Property Let State(ByVal RHS As Variant)
    StateManager.Data = RHS
    Me.Read StateManager
End Property

Обратите внимание, что Statemanager - это поток, который читает и хранит байтовые массивы.

Этот код повторяется десятки раз.

Теперь в .NET я могу обойти это, используя комбинацию обобщений и наследования. Мой объект в версии .NET получает Assign, Clone и State, когда они наследуются от MyAppBaseObject. Но мне не нравится тот факт, что каждый объект наследуется от MyAppBaseObject.

Я просто смешиваю в интерфейсе Assign Clone И ПОВЕДЕНИИ. Еще лучше смешивать отдельно интерфейс Read и Store, а затем смешивать в Assign и Clone. На мой взгляд, это был бы более чистый код.

Но время, когда я повторно использую поведение, увеличивается во времени при использовании интерфейса. Это потому, что цель большинства иерархий объектов не в том, чтобы повторно использовать поведение, а в том, чтобы точно определить отношения между различными объектами. Для каких интерфейсов предназначены. Так что, хотя было бы неплохо, чтобы C # (или VB.NET) имел такую ​​возможность, на мой взгляд, это не ограничитель показа.

Вся причина в том, что это даже проблема в том, что C ++ сначала шарил, когда дело дошло до интерфейса с проблемой наследования. Когда ООП дебютировал, все думали, что повторное использование поведения было приоритетом. Но это оказалось химерой и полезно только для конкретных обстоятельств, таких как создание структуры пользовательского интерфейса.

Позже была разработана идея миксинов (и других связанных концепций в аспектно-ориентированном программировании). Множественное наследование оказалось полезным при создании дополнений. Но C # был разработан как раз перед тем, как это было широко признано. Вероятно, для этого будет разработан альтернативный синтаксис.

1 голос
/ 22 февраля 2009

Это правда, что композиция интерфейса (подобный Java или C #) плюс пересылка к помощнику может эмулировать многие из распространенных применений множественного наследования (особенно mixins). Однако это делается за счет того, что код пересылки повторяется (и нарушает DRY).

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

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

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

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