22

谁能想到使用多重继承的任何情况?我能想到的每一个案例都可以通过方法操作符来解决

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

12 回答 12

30

全面多重继承的大多数用途是用于混合。举个例子:

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

ETC...

大多数情况下,最好使用多重继承来做严格的接口继承。

class DraggableWindow : Window, IDraggable { }

然后在 DraggableWindow 类中实现 IDraggable 接口。编写好的 mixin 类太难了。

MI 方法的好处(即使您只使用 Interface MI)是您可以将各种不同的 Windows 视为 Window 对象,但可以灵活地创建单个不可能(或更困难)的东西遗产。

例如,在许多类框架中,您会看到如下内容:

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

现在,假设您想要一个具有 Window 特性的文本框?就像是可拖动的,有一个标题栏等......你可以做这样的事情:

class WindowedTextbox : Control, IWindow, ITexbox { }

在单继承模型中,您不能轻松地从 Window 和 Textbox 继承而不会遇到重复的 Control 对象和其他类型的问题。您还可以将 WindowedTextbox 视为窗口、文本框或控件。

此外,为了解决您的 .anotherClass() 习语,.anotherClass() 返回一个不同的对象,而多重继承允许将同一对象用于不同的目的。

于 2009-02-21T23:49:56.647 回答
14

我发现多重继承在使用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(); }
}

在进行任何形式的公共继承(单个或多个)时,最重要的是尊重关系。如果一个类“是”这些对象之一,则它应该只从一个或多个类继承。如果它只是“包含”这些对象之一,则应改为使用聚合或组合。

上面的例子结构很好,因为狗是动物,也是脊椎动物。

于 2009-02-21T23:40:16.280 回答
5

大多数人在将多个接口应用于类的上下文中使用多重继承。这是 Java 和 C# 等强制执行的方法。

C++ 允许您在类型之间以 is-a 关系相当自由地应用多个基类。因此,您可以将派生对象视为其任何基类。

LeopardSkinPillBoxHat 指出,另一种用途是混合。Loki library就是一个很好的例子,来自 Andrei Alexandrescu 的 Modern C++ Design 一书。他使用他所称的策略类,通过继承指定给定类的行为或要求。

另一种用途是简化模块化方法,该方法通过在经常令人恐惧的菱形层次结构中使用姐妹类委托来实现API 独立性。

MI的用途很多。滥用的可能性更大。

于 2009-02-21T23:48:01.630 回答
5

Java有接口。C++ 没有。

因此,可以使用多重继承来模拟接口特性。如果您是 C# 和 Java 程序员,每次使用扩展基类但也实现一些接口的类,您都承认多重继承在某些情况下可能很有用。

于 2009-02-21T23:50:36.943 回答
5

我认为这对样板代码最有用。例如,IDisposable 模式对于 .NET 中的所有类都是完全相同的。那么为什么要一遍又一遍地重新键入该代码呢?

另一个例子是 ICollection。绝大多数接口方法的实现完全相同。只有几个方法实际上是您的类所独有的。

不幸的是,多重继承很容易被滥用。人们很快就会开始做一些愚蠢的事情,比如 LabelPrinter 类继承自他们的 TcpIpConnector 类,而不是仅仅包含它。

于 2009-02-21T23:52:48.123 回答
3

我最近处理的一个案例涉及启用网络的标签打印机。我们需要打印标签,所以我们有一个类 LabelPrinter。这个类有打印几个不同标签的虚拟调用。我还有一个用于 TCP/IP 连接事物的通用类,它可以连接、发送和接收。因此,当我需要实现打印机时,它继承自 LabelPrinter 类和 TcpIpConnector 类。

于 2009-02-21T23:40:26.797 回答
3

我认为 fmsf 示例是一个坏主意。汽车不是轮胎或发动机。你应该为此使用组合。

MI(实现或接口)可用于添加功能。这些通常被称为 mixin 类。想象一下你有一个 GUI。有处理绘图的视图类和处理拖动的拖放类。如果你有一个对象,你会有一个像

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

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

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

}
于 2009-02-21T23:45:53.520 回答
2

确实,接口的组合(Java 或 C# 之类的)加上转发到帮助程序可以模拟多重继承的许多常见用途(尤其是 mixins)。然而,这是以重复转发代码(并违反 DRY)为代价的。

MI 确实打开了许多困难的领域,最近一些语言设计者已经做出决定,认为 MI 的潜在缺陷大于好处。

类似地,人们可以反对泛型(异构容器确实有效,循环可以用(尾)递归代替)和几乎任何其他编程语言特性。仅仅因为可以在没有特性的情况下工作并不意味着该特性没有价值或无法帮助有效地表达解决方案。

丰富多样的语言和语言系列使我们作为开发人员更容易选择解决手头业务问题的好工具。我的工具箱里有很多我很少使用的东西,但在那些场合我不想把所有东西都当作钉子。

于 2009-02-22T00:38:02.117 回答
2

我们的产品如何使用 mixin 类的一个示例是用于配置保存和恢复目的。有一个抽象的 mixin 类,它定义了一组纯虚方法。任何可保存的类都继承自保存/恢复 mixin 类,该类会自动为它们提供适当的保存/恢复功能。

这个例子并没有真正说明多重继承的用处。这里定义的是一个接口。多重继承也允许您继承行为。这是mixins的重点。

一个例子; 由于需要保持向后兼容性,我必须实现自己的序列化方法。

所以每个对象都有一个像这样的读取和存储方法。

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 版本下的对象从 MyAppBaseObject 继承时会获得分配、克隆和状态。但我不喜欢每个对象都继承自 MyAppBaseObject 的事实。

我宁愿只是混合分配克隆界面和行为。更好的是单独混合读取和存储界面,然后能够混合分配和克隆。在我看来,这将是更简洁的代码。

但是当我使用接口时,我重用行为的时间已经相形见绌了。这是因为大多数对象层次结构的目标不是重用行为,而是精确定义不同对象之间的关系。设计用于哪些接口。因此,虽然 C#(或 VB.NET)有一些能力来做到这一点会很好,但在我看来,这并不是一个阻碍。

这甚至是一个问题的全部原因是 C++ 在谈到接口与继承问题时一开始就搞砸了。当 OOP 出现时,每个人都认为行为重用是首要任务。但这被证明是一种嵌合体,仅对特定情况有用,例如制作 UI 框架。

后来,mixin(以及面向方面编程中的其他相关概念)的想法得到了发展。发现多重继承在创建混合中很有用。但是 C# 是在这被广泛认可之前开发的。可能会开发一种替代语法来执行此操作。

于 2009-02-23T20:29:00.937 回答
2

我怀疑在 C++ 中,MI 最好用作框架的一部分(前面讨论过的混合类)。我唯一确定的是,每次我尝试在我的应用程序中使用它时,我最终都会后悔选择,并经常将其撕掉并用生成的代码替换它。

MI 是另一种“如果你真的需要就使用它,但要确保你真的需要它”的工具。

于 2009-02-23T20:56:22.057 回答
1

以下示例主要是我在 C++ 中经常看到的内容:有时由于您需要的实用程序类,它可能是必要的,但由于它们的设计不能通过组合使用(至少效率不高或不会使代码比回退更混乱)多重继承)。一个很好的例子是你有一个抽象基类 A 和一个派生类 B,而 B 也需要是一种可序列化的类,所以它必须派生自另一个名为 Serializable 的抽象类。可以避免 MI,但是如果 Serializable 只包含一些虚拟方法并且需要对 B 的私有成员进行深度访问,那么为了避免进行友元声明并将 B 的内部访问权泄露给某些人,可能值得混淆继承树助手组合类。

于 2009-02-21T23:46:08.810 回答
1

我今天必须使用它,实际上...

这是我的情况 - 我有一个在内存中表示的域模型,其中 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

实际实现是在带有接口和泛型的 C# 中实现的,如果语言支持它,我不能像我那样做多重继承(必须完成一些复制粘贴)。所以,我想我会搜索一下,看看人们对多重继承的看法,我得到了你的问题;)

我无法使用您的 .anotherClass 解决方案,因为实现了 add for Parent (引用这个 - 我希望这不是其他类)。

它变得更糟,因为生成的代码有一个子类,它既不是父母也不是孩子……更多复制粘贴。

于 2009-04-30T00:46:49.213 回答