2009-02-21 6 views
15

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

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

Это ответ на ваш вопрос?Если да, примите ответ. (Подсказка: +12 ответ может быть ответом) – 2010-01-03 12:31:46

ответ

21

Большинство применений полной шкалы Множественное наследование для миксинов. В качестве примера:

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 { } 

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

class WindowedTextbox : Control, IWindow, ITexbox { } 

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

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

12

Я нахожу множественное наследование особенно полезно при использовании 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(); } 
} 

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

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

+0

Это не множественное наследование. Это использование интерфейсов, но C++ просто реализует его таким же образом. – erikkallen

+0

@Erik - Это только интерфейс, если класс mixin полностью абстрактный. В примере кода, который я дал, Vertebrate :: BendSpine() имеет реализацию, хотя и пустую. Я бы назвал это множественное наследование, даже если это не лучший пример. – LeopardSkinPillBoxHat

+1

Не было бы больше смысла, чтобы позвоночный был подклассом животного, а для собаки - просто подкласс Позвоночных. Последнее, что я проверил, все Позвоночные животные. – Kibbee

2

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

2

Я думаю, что пример 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{ 

} 
0

Следующий пример в основном то, что я вижу часто в C++: иногда это может быть необходимо из-за полезные классы, которые вам нужны, но из-за их конструкцию не может использоваться по составу (по крайней мере, неэффективно или не делает код даже более беспорядочным, чем возврат на наследование). Хорошим примером является то, что у вас есть абстрактный базовый класс A и производный класс B, а B также должен быть своего рода сериализуемым классом, поэтому он должен образовывать, скажем, еще один абстрактный класс, называемый Serializable.Можно избежать MI, но если Serializable содержит только несколько виртуальных методов и нуждается в глубоком доступе к частным членам B, то, возможно, стоит замалчивать дерево наследования только для того, чтобы избежать объявления друга и предоставления доступа к внутренним частям B к некоторым класс вспомогательной композиции.

4

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

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

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

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

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

4

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

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

+0

Я согласен с этим :) Хуже всего то, что вы изучаете C++, а затем C# позже - это ограничение миксинов. Иногда я нахожу себя вставкой для копирования, реализующей интерфейс mixin, который может быть плохой код или дизайн для моей части, но я не могу придумать ничего другого, и как программиста, которого вы ненавидите copy-paste – cwap

+0

C++ эмулирует интерфейсы 100% с абстрактными классами. О какой «функции интерфейса» вы говорите? Java/C# версия множественного наследования для интерфейсов? –

+0

У C++ нет ключевого слова 'interface', поскольку оно не нуждается в нем (оно имеет множественное наследование). Если на других языках, таких как Java и C#, есть интерфейсы, тогда несколько наследований на C++ должны иметь некоторые варианты использования. – luiscubal

2

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

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

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

+0

Теоретически вы правы, но на практике это очень сложно. И цена (конструкторы виртуального наследования) довольно высока. –

+0

Эта цена выплачивается только в том случае, если вам нужно иметь общий наследуемый через несколько ветвей наследования. На практике это редко случается (и становится не-проблемой, если повторная база не имеет состояния). – Richard

+1

«Люди быстро начнут делать такие глупые вещи, как класс LabelPrinter, наследуемый от своего класса TcpIpConnector, а не просто содержать его». В самом деле? Я слышал это много, но никогда никаких доказательств этого на самом деле не происходит. Весь материал, который я когда-либо читал, делает довольно четкое различие между композицией и наследованием, и я не вижу, как кто-то может сбить с толку. –

1

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

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

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

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

+1

Если языковая функция потенциально очень полезна, но, как правило, она будет плохо злоупотреблять, она, скорее всего, будет присутствовать на C++ и отсутствует на Java. Различные философии языка. –

+1

Да. Java предназначен для идиотов. –

1

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

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

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

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

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, я использую интерфейс. Это связано с тем, что целью большинства иерархий объектов НЕ является повторное использование поведения, но точное определение взаимосвязи между различными объектами. Для каких интерфейсов предназначены. Поэтому, хотя было бы неплохо, что у C# (или VB.NET) была какая-то возможность сделать это, по моему мнению, это не пробная пробка.

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

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

1

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

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

0

я должен был использовать его сегодня, на самом деле ...

Вот моя ситуация - у меня была модель предметной области, представленной в памяти, где А содержит ноль или более 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 

Фактическая реализация была в C# с интерфейсами и дженериками, и я не мог выполнять множественное наследование, как если бы язык поддерживал его (некоторая скопированная копия должна была быть выполнена). Итак, я думал, что буду искать SO, чтобы узнать, что люди думают о множественном наследовании, и я задал ваш вопрос;)

Я не мог использовать ваше решение .anotherClass из-за реализации add для Parent (ссылается на это - и я хотел, чтобы это не было каким-то другим классом).

Это ухудшилось, потому что сгенерированный код имел подкласс под другим, который не был ни родителем, ни ребенком ... больше копировать.

+1

Кажется, вы делаете это более сложным, чем необходимо. Почему бы просто не иметь «узел», который имеет один родительский элемент (который может быть NULL) и несколько дочерних элементов (которые могут быть пустыми). Затем просто сделайте A, B, C и D все наследуемыми от Node. –

+0

Я хотел сказать, что вы не можете ссылаться на родителя A или добавить к ребенку D (потому что A не имеет родителя, а D не имеет детей). С моей реализацией он не будет компилироваться, если вы попытаетесь. Я также улучшил это с помощью дженериков, чтобы вы не могли добавить C в A, C или D, или если вы ссылаетесь на родителя B, вам не нужно будет передавать его в A (это будет A уже через дженерики) и т. д. – paquetp

Смежные вопросы