Oop 继承与组合-特定案例

Oop 继承与组合-特定案例,oop,inheritance,composition,software-design,Oop,Inheritance,Composition,Software Design,我一直在读关于继承与组合的书。我知道这个网站上已经有关于这个主题的文章,但是我有一个关于这个具体例子的问题,我希望不会被视为重复 到目前为止,我收集了一些指导原则: 喜欢组合而不是继承 因此,Has-a关系几乎总是意味着合成 Is-a关系可能意味着继承是最好的,但并不总是最好的 现在举个例子 我一直在做一个简单的游戏引擎来练习C++。为此,我开设了一些课程: 包含可渲染图像的图像类 Transform类是关于定位的 Sprite类包含可以在特定位置渲染的图像 当然还有更多,但让我们保持

我一直在读关于继承与组合的书。我知道这个网站上已经有关于这个主题的文章,但是我有一个关于这个具体例子的问题,我希望不会被视为重复

到目前为止,我收集了一些指导原则:

  • 喜欢组合而不是继承
  • 因此,Has-a关系几乎总是意味着合成
  • Is-a关系可能意味着继承是最好的,但并不总是最好的

现在举个例子 我一直在做一个简单的游戏引擎来练习C++。为此,我开设了一些课程:

  • 包含可渲染图像的图像类
  • Transform类是关于定位的
  • Sprite类包含可以在特定位置渲染的图像
当然还有更多,但让我们保持简单的例子


所以-Sprite有一个图像,有一个位置(变换)。遵循这一指导原则,这意味着双方都要进行合作。但我认为让精灵从变换中继承要容易得多

因为我知道如何处理sprite->setPosition()和sprite->setScale()

  • 如果精灵从Transform继承,我不必做任何事情,Sprite->setPosition()会自动为精灵对象调用Transform::setPosition()

  • 如果Sprite有一个转换,我必须将所有这些方法重定向到转换!(这是我到目前为止一直在做的事情,看起来效果不错) 我不想为每种定位方法都写这样的东西:(已经有很多了)

但它似乎违背了惯例。 你们都怎么想?

当你们说“更容易”时,我想你们的意思是“更快”,因为让我们面对现实,将调用从Sprite转发到Transform组件一点都不困难,只需要更多的输入(而“更少的输入”是好的,但我会先权衡其他事情)。选择继承而不是组合的问题现在不可能存在,但以后可能会出现

使用公共继承,您只需声明继承,并且来自Transform的所有公共接口都可以在Sprite中使用。全部的如果有一天你在变换中添加了一个“Rotate”方法,但是不想让精灵颠倒显示,那么你就必须隐藏基类方法。马蒂厄和CodeFirst关于如何在C++中隐藏这一点有很好的答案。但正如Matthieu指出的那样,隐藏方法违反了Liskov替换原则。如果您使用了composition,那么就可以忽略Sprite公共界面中的“Rotate”方法

使用私有继承会更好一些,因为它代表了Sprite和Transform之间的“实现方式”关系,比公共继承所代表的“是”关系更接近现实。但是,您需要使用“using”语句来提供Sprite中Transform的方法(参见前面链接中的Eugen-answer)。因此,您最终仍然需要编写额外的代码来实现您的目标(尽管比使用组合代码要少)

通过组合,我觉得你有更容易的选择来处理设计问题。您想简单地调用组件方法吗?你转接电话。是否要禁止组件方法?您不会将其添加到Sprite界面。是否要向一种方法添加功能(例如,不要将变换比例设置为精灵图像太小的值)?将该方法添加到接口中,但在调用转换组件之前/之后执行一些额外的处理/检查


如果你能掌握Scott Meyers的有效C++书籍,你有两个值得阅读的项目:“明智地使用私人继承”和“模型”Has-A或“以合成”来实现。最后一点是对组合的支持:“组合和私有继承都是根据实现的,但是组合更容易理解,所以您应该尽可能地使用它”

难道您不能让成员函数返回对转换的引用吗?然后,您不需要手动转发调用,也不需要使用接口(纯虚拟类)公开Is-a和组合来实现它。将接口上的调用转发给实现成员对象。要更改行为,请使用不同的合成对象。
Sprite::setPosition(Vector2 position) {
    mTransform.setPosition(Vector2 position);
}