与原来的示例代码
有许多与你的榜样问题怀疑的问题。
首先,您不必在基类中提供函数体。改用纯虚函数。其次,你的类D1和D2都没有功能,所以它们应该是抽象的(这会阻止你从它们中创建被剥夺的对象)。如果你真的为你的基类使用纯虚拟函数,这第二个问题将变得清晰。编译器将开始发出警告。
与D134一样,将D1例化为new D1
,这是糟糕的设计,因为D1没有setLength方法的真正功能实现,即使您给它一个“虚拟”主体。给它一个'虚拟'身体(一个没有做任何有用的东西),所以掩盖了你的设计错误。
所以你的评论(但我不喜欢不得不把默认函数放在基类中)证明了一个适当的直觉。必须这样做的信号有缺陷的设计。 D1对象无法理解setLength,而其继承的公共接口承诺它可以。
并且:如果使用正确,多继承没有任何问题。它非常强大和优雅。但你必须在适当的地方使用它。 D1和D2是B的部分实现,所以抽象,并从两者继承将确实给你一个完整的实现,非常具体。
也许一个很好的规则是:只有在看到引人注目的需求时才使用多重继承。但是,如果你这样做,这是非常有用的。与例如相比,它可以防止相当丑陋的不对称和代码重复。像Java这样的语言已经禁止了它。
我不是树医生。当我使用链锯时,会危害我的腿。但这并不是说电锯没有用处。
在哪里放假人:无处请,不要取消...
[编辑OP的第一个注释后]
如果你得到的B类D1,将打印的“未实现setLength”如果你调用它的setLength方法,应该如何调用者作何反应?它不应该首先调用它,如果D1不是从具有这种方法的B派生的,那么调用者可能已经知道这个方法,纯粹是虚拟的。那么很明显,它不支持这种方法。拥有B基类让D1感到宾至如归的元素类型,B *或B &的多态数据结构承诺其用户正确支持getLength,而不支持getLength。
尽管在你的例子中情况并非如此(但也许你遗漏了一些东西),但当然可以有一个很好的理由从B中推导出D1和D2。B可以保存最终接口的一部分或实现它的派生类D1和D2都需要。
假设B有一个方法setAny(key,value)(设置字典中的一个值),D1和D2都使用,D1在setColor中调用它,D2在setLength中调用它。 在这种情况下,使用通用基类是有道理的。在这种情况下,B不应该有虚拟方法setColor或setLength,既不是虚拟方法也不是纯粹方法。你应该只在你的D1类中有一个setColor,在你的D2类中有一个setLength,但在你的B类中都没有。
有一个在面向对象设计的基本原则:
不要剥夺继承权
通过引入“方法,这是不适用”的具体类的概念,这正是你正在做。现在像这样的规则不是教条。但违反这条规则几乎总是指向一个设计缺陷。
所有B的一个数据结构是唯一有用的有他们这样做一招,他们都明白...
[EDIT2 OP的第二COMENT后]
OP希望有一个地图,可以容纳来自B的任何类别的对象。
这正是问题出现的地方。要了解如何存储指向我们对象的指针和引用,我们必须问:用于存储的是什么。如果一个地图,比如说mapB被用来存储指向B的指针,那么必须有一个点。数据存储的乐趣在于检索数据并做一些有用的事情。
让我们通过使用日常生活中的清单来简化这一点。假设我有一个personList 1000人,每个人都有他们的全名和电话号码。现在说我的厨房水槽有问题。我实际上可以通读清单,打电话给每个人,问:你能修理我的厨房水槽吗?换句话说:你是否支持repairKitchenSink方法。或者:你有没有可能成为水管工人的一个例子(你是水管工)。但是后来我花了很长时间打电话,或许经过500次电话会议后,我很幸运。
现在我personList上的所有1000人都支持talkToMe方法。所以,每当我感到孤独时,我都可以打电话给任何人,并调用该人的talkToMe方法。但他们不应该都有修理KidchenSink方法,即使不是纯粹的虚拟或虚拟变体也会做别的事情,因为如果我将这种方法称为类Burglar的人,他可能会响应这个呼叫,但是在一个意外的方式。
所以类人不应该包含一个方法repairKitchenSink,即使不是一个纯粹的虚拟人。因为它不应该被称为personList迭代的一部分。在迭代plumberList时应该调用它。该列表仅保存支持repairKitchenSink方法的对象。
使用纯虚函数只有在适当
他们可能会支持它以不同的方式,但。换句话说,在Plumber类中,方法repairKitchenSink可以例如是纯粹的虚拟。有可能例如是2派生类,PvcPlumber和CopperPlumber。 CopperPlumber将通过调用lightFlame实现(代码)repairKitchenSink方法,然后调用solderDrainToSink,而PvcPlumber将实现它作为applyGlueToPvcTube和glueTubeToSinkOutlet的连续调用。但是这两个水管工子类仅以不同的方式实施repairKitchenSink。这并且唯一证明在他们的基类水暖工中拥有纯虚函数repairKitchenSink。当然,一个班级可能来自水管工,不会实施该方法,比如说WannabePlumber类。但是由于它是抽象的,你不能从它实例化对象,这很好,除非你想湿脚。
Person可能有很多不同的子类。他们例如代表不同的职业,或不同的政治偏好或不同的宗教。如果一个人是民主党的Budhist Plumber,那么他(M/F)可能在继承民主党,Budhist和水管工阶级的衍生阶级。使用继承或者甚至为政治偏好或宗教信仰,甚至职业以及这些组合的无尽组合这样动荡的东西打字,在实践中并不方便,但这仅仅是一个例子。在现实中,职业,宗教和政治偏好可能是属性。但这并没有改变这里重要的一点。如果某个类是不支持某种操作的,那么它不应该在数据结构中暗示它的作用。
除了personList,拥有plumberList,animistList和democratList之外,您一定会打电话给理解您的方法inviteBillToPlayInMyJazzBand或worshipTheTreeInMyBackyard的人。
列表不包含对象,它们只包含指向对象的指针或引用。所以我们的民主Budhist管道工被包含在personList,democratList,budhistList和plumberList中没有任何问题。列表就像数据库索引。不包含记录,他们只是指它们。你可以在一个表上有许多索引,而且你应该这么做,因为索引很小,并且使你的数据库更快。
对于多态数据结构也是如此。目前,即使personList,democratList,budhistList和plumberList变得如此之大以至于内存不足,解决方案通常不会只有一个personList。因为那样你就会遇到一个性能问题和一个代码复杂性问题,而这个问题通常会变得更糟。
所以,回到你的评论:你说你想让你所有的派生类都在B的列表中。很好,但是B的接口应该只包含为列表中的所有内容实现的方法,所以没有虚拟方法。这就像是通过图书馆并浏览所有书籍,寻找一种支持教学关于浮萍的方法。老实说,告诉你所有这些,我一直犯下一个资本罪。我一直在推销一般真理。但在软件设计中这些不存在。我一直在试图将它们出售给你,因为我已经教了30年的面向对象设计,而且我认为我认识到了你卡在哪里。但是对于每一条规则,都有很多例外。尽管如此,如果我已经正确地解决了你的问题,在这种情况下,我认为你应该选择单独的数据结构,每个数据结构只保存对象的引用或指针,这些对象确实可以在你迭代特定数据结构时进行欺骗。
点是方形圆
部分的混乱中适当地使用多态数据结构(数据结构保持指针或引用不同的对象类型)来对关系数据库的世界。 RDB与平面记录表一起工作,每个记录具有相同的字段。由于某些领域可能不适用,所以发明了一种叫做“约束”的东西。在C++类中,Point将包含字段x和y。 Class Circle可以继承它,并且还包含字段'radius'。类Square也可以继承Point,但除x和y之外还包含字段“side”。在RDB世界约束中,不是字段,是继承的。因此,一个圆的约束半径将为== 0,而一个Square的约束点的值为== 0.一个点将继承两个约束,所以它将满足既是正方形又是圆的条件:一个点是一个正方形这在数学上确实是这样。请注意,与C++相比,约束继承层次结构是'颠倒'的。这可能会让人困惑。
无论是普遍认为继承与专业化并驾齐驱,两者都无济于事。虽然通常情况下并不总是如此。在许多情况下,C++继承是扩展而不是专业化。这两者往往是一致的,但点,方,圆的例子表明,这不是一个普遍的事实。
如果使用继承,在C++中Circle应该从Point派生,因为它有额外的字段。但是Circle肯定不是一种特殊类型的Point,反之亦然。在许多实际的图书馆中,Circle将包含一个类Point的对象,持有x和y,而不是从它继承,绕过了整个问题。
欢迎的设计选择
你碰到了什么,世界是一个真正的设计选择,一个重要问题。仔细思考这样的事情,就像你在做的一样,在实践中尝试所有这些事情,包括所谓的“错误”,都会使你成为程序员,而不是编码员。
没有具体的例子就很难说出任何东西。继承代表的是一种关系。通常,当你开始谈论向对象添加“行为和更多数据”时,这是否意味着它总是使用行为和数据,或者它是可选的?如果这样查看组件和组合而不是继承和运行时查询附加行为。如果你真的觉得你会继续扩展你的基本类型,这种方法更加模块化。 –
您可能想了解[纯虚方法](http://stackoverflow.com/questions/1306778/c-virtual-pure-virtual-explained) – wasthishelpful
单继承是相当自然的,以确保整个层次结构将尊重共享概念;多重继承(没有虚拟)对于为同一个对象提供不同的视图可能很有用。应谨慎使用多个虚拟继承(dynamic_cast从基础派生到派生需要),正如您所提到的,复合或装饰器模式可能是有趣的替代方案。 – Franck