首先,组合的替代方案是私有继承(而不是公共继承),因为两者都建立了具有关系的模型。
重要的问题是我们如何将Sprite
公共成员(例如changeImage
)暴露给VisibleGameObject
客户?我介绍了我知道的 4 种方法:
(私有)继承
我了解您想避免(多重)继承,但为了完整起见,我提出一个基于私有继承的建议:
class VisibleGameObject: private Sprite, public GameObject {
...
};
在这种情况下VisibleGameObject
,私下派生自Sprite
. 然后前者的用户不能访问后者的任何成员(就好像它是私人成员一样)。特别是,Sprite
的公共和受保护成员对VisibleGameObject
客户是隐藏的。
如果继承是公开的,那么所有 Sprite
的公开和受保护成员都将暴露VisibleGameObject
给它的客户。使用私有继承,我们可以更好地控制应该通过使用声明公开哪些方法。例如,这暴露了Sprite::changeImage
:
class VisibleGameObject1: private Sprite, public GameObject {
public:
using Sprite::changeImage;
...
};
转发方式
VisibleGameObject
我们可以将调用转发给公共方法,m_sprite
如下所示。
class VisibleGameObject2: public GameObject {
public:
void changeImage() {
m_sprite.changeImage();
}
private:
Sprite m_sprite;
...
};
我相信这是最好的设计,尤其是在封装方面。但是,相对于其他替代方案,它可能需要大量输入。
结构解引用运算符
即使是普通的旧 C 也提供了暴露另一种类型的接口的类型,就好像它是它自己的一样:指针。
确实,假设它p
是 type Sprite*
。然后通过使用结构解引用运算符->
,我们可以访问Sprite
(指向p
)的成员,如下所示。
p->changeImage();
C++ 允许我们为类赋予定制的结构取消引用运算符(智能指针很好地使用的一个特性)。我们的例子变成:
class VisibleGameObject3 : public GameObject {
public:
Sprite* operator ->() {
return &m_sprite;
}
private:
Sprite m_sprite;
...
};
和
VisibleGameObject v;
v->changeImage();
这种方法虽然方便,但也有很多缺陷:
- 至于公共继承,这种方法不能很好地控制
Sprite
应该公开哪些公共成员。
- 它仅适用于一个成员(也就是说,您不能使用相同的技巧来公开两个成员接口)。
- 它与界面混淆。确实,例如考虑
VisualGameObject
有一个方法doSomething()
。v
然后,在一个应该做的对象上调用这个方法,v.doSomething()
而调用changeImage()
一个应该使用v->changeImage()
. 这令人困惑。
- 它
VisibleGameInterface
看起来像一个智能指针。这在语义上是错误的!
C++11 包装模式
最后,还有 Sutter 的 C++11 Wrapper Pattern(观看他的演示文稿,特别是第 9 页的第二张幻灯片):
class VisibleGameObject4 : public GameObject {
private:
Sprite m_sprite;
public:
template <typename F>
auto operator()(F f) -> decltype(f(m_sprite)) {
return f(m_sprite);
}
};
客户这样使用它:
VisibleGameObject4 v4;
v4( [](Sprite& s) { return s.changeImage(); } );
正如我们所看到的,与转发方法相比,这种方法将打字的负担从类编写者转移到了类客户端。