Использование для множественного наследования?
Может ли кто-нибудь подумать о любой ситуации, чтобы использовать множественное наследование? Каждый случай, о котором я могу думать, может быть разрешен оператором метода
AnotherClass() { return this->something.anotherClass; }
Ответы
Ответ 1
Большинство применений полномасштабного множественного наследования для миксинов. В качестве примера:
class DraggableWindow : Window, Draggable { }
class SkinnableWindow : Window, Skinnable { }
class DraggableSkinnableWindow : Window, Draggable, Skinnable { }
и т.д...
В большинстве случаев лучше всего использовать множественное наследование, чтобы строго наследовать интерфейс.
class DraggableWindow : Window, IDraggable { }
Затем вы реализуете интерфейс IDraggable в своем классе DraggableWindow. Слишком сложно писать хорошие классы mixin.
Преимущество подхода MI (даже если вы используете только интерфейс MI) - это то, что вы можете обрабатывать все виды различных Windows как объекты Window, но иметь гибкость для создания вещей, которые не были бы возможны (или более сложными ) с единственным наследованием.
Например, во многих фреймворках классов вы видите что-то вроде этого:
class Control { }
class Window : Control { }
class Textbox : Control { }
Теперь предположим, что вам нужны текстовые поля с характеристиками окна? Подобно тому, как можно перетаскивать, иметь заголовок и т.д. Вы можете сделать что-то вроде этого:
class WindowedTextbox : Control, IWindow, ITexbox { }
В одиночной модели наследования вы не можете легко наследовать как из окна, так и из текстового поля без каких-либо проблем с дублирующими объектами Control и другими проблемами. Вы также можете рассматривать WindowedTextbox как окно, текстовое поле или элемент управления.
Кроме того, чтобы обратиться к вашей .anotherClass() idiom,.anotherClass() возвращает другой объект, а множественное наследование позволяет использовать один и тот же объект для разных целей.
Ответ 2
Я нахожу многократное наследование особенно полезным при использовании классов mixin.
Как указано в Википедии:
В объектно-ориентированном программировании языков, mixin - это класс, который обеспечивает определенную функциональность унаследованный подклассом, но не предназначенный для самостоятельной работы.
Пример того, как наш продукт использует классы mixin, предназначен для сохранения и восстановления конфигурации. Существует абстрактный класс mixin, который определяет набор чистых виртуальных методов. Любой класс, который можно сохранить, наследует класс сохранения/восстановления mixin, который автоматически предоставляет им соответствующие функции сохранения/восстановления.
Но они могут также наследоваться от других классов как часть их нормальной структуры классов, поэтому для этих классов довольно часто использовать множественное наследование.
Пример множественного наследования:
class Animal
{
virtual void KeepCool() const = 0;
}
class Vertebrate
{
virtual void BendSpine() { };
}
class Dog : public Animal, public Vertebrate
{
void KeepCool() { Pant(); }
}
Что наиболее важно при выполнении любой формы публичного наследования (одного или нескольких), является уважение отношения . Класс должен наследовать только один или несколько классов, если он "является" одним из этих объектов. Если он просто "содержит" один из этих объектов, вместо него следует использовать агрегацию или состав.
Приведенный выше пример хорошо структурирован, потому что собака является животным, а также позвоночным.
Ответ 3
Большинство людей используют множественное наследование в контексте применения нескольких интерфейсов к классу. Это подход Java и С#, среди прочих, принудительный.
С++ позволяет вам применять несколько базовых классов довольно свободно, в отношениях между типами. Таким образом, вы можете обрабатывать производный объект, как любой из его базовых классов.
Другое использование, поскольку указывает LeopardSkinPillBoxHat, находится в миксах. Прекрасным примером этого является библиотека Loki, из книги Андрея Александреску "Современный дизайн С++". Он использует то, что он описывает классы политики, которые определяют поведение или требования данного класса через наследование.
Еще одно использование - это упрощение модульного подхода, позволяющего независимость от API посредством использования делегирования схожих классов в частотном диапазоне, страшной алмазной иерархии.
Использования для ИМ много. Возможность злоупотребления еще больше.
Ответ 4
Java имеет интерфейсы. С++ не имеет.
Следовательно, множественное наследование может использоваться для эмуляции функции интерфейса.
Если вы программист на С# и Java, каждый раз, когда вы используете класс, который расширяет базовый класс, но также реализует несколько интерфейсов, вы можете признать, что множественное наследование может быть полезно в некоторых ситуациях.
Ответ 5
В одном случае я работал над недавно включенными сетевыми принтерами этикеток. Нам нужно печатать ярлыки, поэтому у нас есть класс LabelPrinter. Этот класс имеет виртуальные вызовы для печати нескольких разных меток. У меня также есть общий класс для связанных TCP/IP вещей, которые могут подключаться, отправлять и получать.
Поэтому, когда мне нужно было реализовать принтер, он унаследовался как от класса LabelPrinter, так и от класса TcpIpConnector.
Ответ 6
Я думаю, что пример fmsf - плохая идея. Автомобиль - это не шина или двигатель. Вы должны использовать композицию для этого.
MI (реализации или интерфейса) может использоваться для добавления функциональности. Они часто называются классами mixin. Представьте, что у вас есть GUI. Существует класс представления, который обрабатывает чертеж и класс Drag & Drop, который обрабатывает перетаскивание. Если у вас есть объект, который делает оба, у вас будет класс вроде
class DropTarget{
public void Drop(DropItem & itemBeingDropped);
...
}
class View{
public void Draw();
...
}
/* View you can drop items on */
class DropView:View,DropTarget{
}
Ответ 7
Я думаю, что это было бы очень полезно для шаблона. Например, шаблон IDisposable абсолютно одинаковый для всех классов в .NET. Итак, зачем переписывать этот код снова и снова?
Другим примером является ICollection. Подавляющее большинство интерфейсных методов реализованы точно так же. Есть только несколько методов, которые действительно уникальны для вашего класса.
К сожалению, множественное наследование очень легко злоупотреблять. Люди быстро начнут делать глупые вещи, такие как класс LabelPrinter, наследовать от своего класса TcpIpConnector, а не просто содержать его.
Ответ 8
Верно, что состав интерфейса (например, Java или С#) плюс пересылка помощнику может эмулировать многие из общих применений множественного наследования (в частности, mixins). Однако это делается за счет того, что этот код пересылки повторяется (и нарушает DRY).
MI открывает ряд сложных областей, и в последнее время некоторые разработчики языка приняли решения о том, что потенциальные ловушки MI перевешивают преимущества.
Аналогично можно спорить против дженериков (гетерогенные контейнеры работают, петли могут быть заменены рекурсией хвоста) и почти любой другой особенностью языков программирования. Просто потому, что можно работать без функции, это не означает, что эта функция бесполезна или не может эффективно выражать решения.
Богатое разнообразие языков и языковых семей облегчает для нас, поскольку разработчики выбирают хорошие инструменты, которые решают деловую проблему под рукой. Мой набор инструментов содержит много элементов, которые я редко использую, но в тех случаях я не хочу рассматривать все как гвоздь.
Ответ 9
Пример того, как наш продукт использует классы mixin, предназначен для сохранения и восстановления конфигурации. Существует абстрактный класс mixin, который определяет набор чистых виртуальных методов. Любой класс, который можно сохранить, наследует класс сохранения/восстановления mixin, который автоматически предоставляет им соответствующие функции сохранения/восстановления.
Этот пример на самом деле не иллюстрирует полезность множественного наследования. Здесь определяется ИНТЕРФЕЙС. Множественное наследование позволяет также наследовать поведение. Какая точка миксинов.
Пример; из-за необходимости сохранения обратной совместимости я должен реализовать свои собственные методы сериализации.
Таким образом, каждый объект получает метод Read и 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 AND BEHAVIOR. Лучше всего смешивать отдельно интерфейс чтения и хранения, а затем смешивать в Assign и Clone. По моему мнению, это был бы более чистый код.
Но времена, когда я использую поведение, DWARFED к моменту использования интерфейса. Это связано с тем, что целью большинства иерархий объектов НЕ является повторное использование поведения, но точное определение взаимосвязи между различными объектами. Для каких интерфейсов предназначены. Поэтому, хотя было бы неплохо, что С# (или VB.NET) имели определенную способность делать это, по моему мнению, это не шоу-стоп.
Вся причина, по которой это даже проблема, что С++ сначала исказил мяч, когда речь зашла об интерфейсе и проблеме наследования. Когда ООП дебютировал, все считали, что повторное использование поведения является приоритетом. Но это оказалось химерой и полезно только для конкретных обстоятельств, например, для создания пользовательского интерфейса.
Позже была разработана идея mixins (и других связанных понятий в аспектно-ориентированном программировании). Множественное наследование было сочтено полезным при создании микширования. Но С# был разработан как раз до того, как это было широко признано. Вероятно, для этого будет разработан альтернативный синтаксис.
Ответ 10
Я подозреваю, что в С++ MI лучше всего использовать как часть фреймворка (ранее обсуждавшиеся классы микширования). Единственное, что я точно знаю, это то, что каждый раз, когда я пытался использовать его в своих приложениях, я заканчивал тем, что сожалел о своем выборе, и часто разрывал его и заменял его сгенерированным кодом.
MI - это еще один из тех, кто использует его, если вам это действительно нужно, но убедитесь, что вы действительно нужны ему.
Ответ 11
Следующий пример - это, в основном, то, что я часто вижу в С++: иногда это может быть необходимо из-за того, что классы полезности вам нужны, но из-за их дизайна нельзя использовать с помощью композиции (по крайней мере, неэффективно или не делая код даже более грязным, чем возвращаясь к многому наследованию). Хорошим примером является то, что у вас есть абстрактный базовый класс A и производный класс B, а B также должен быть своего рода сериализуемым классом, поэтому он должен получить, скажем, еще один абстрактный класс под названием Serializable. Можно избежать MI, но если Serializable содержит только несколько виртуальных методов и нуждается в глубоком доступе к частным членам B, то может быть стоить замалчивать дерево наследования только для того, чтобы избежать объявления друга и предоставления доступа к внутренним B для некоторых класс вспомогательной композиции.
Ответ 12
Мне пришлось использовать его сегодня, на самом деле...
Вот моя ситуация: у меня была модель домена, представленная в памяти, где A содержал нуль или более Bs (представленный в массиве), каждый B имел ноль или более Cs, а Cs - Ds. Я не мог изменить тот факт, что они были массивами (источник для этих массивов был из автоматически сгенерированного кода из процесса сборки). Каждый экземпляр должен был отслеживать индекс в родительском массиве, в котором они были. Они также должны были отслеживать экземпляр своего родителя (слишком подробно о том, почему). Я написал что-то вроде этого (было больше, и это не синтаксически правильно, это просто пример):
class Parent
{
add(Child c)
{
children.add(c);
c.index = children.Count-1;
c.parent = this;
}
Collection<Child> children
}
class Child
{
Parent p;
int index;
}
Затем для типов домена я сделал следующее:
class A : Parent
class B : Parent, Child
class C : Parent, Child
class D : Child
Фактическая реализация была в С# с интерфейсами и generics, и я не мог выполнять множественное наследование, как если бы это был язык, поддерживаемый им (некоторая скопированная копия должна была быть выполнена). Итак, я думал, что буду искать SO, чтобы узнать, что люди думают о множественном наследовании, и я задал ваш вопрос;)
Я не мог использовать ваше решение .anotherClass из-за реализации add для Parent (ссылается на это - и я хотел, чтобы это не было каким-то другим классом).
Ухудшилось, потому что сгенерированный код имел подкласс под другим чем-то, что не было ни родителем, ни ребенком... больше копировать.