如何处理组合而不是继承?考虑以下课程:
class GameObject {...};
class Sprite {
public:
void changeImage(...);
};
class VisibleGameObject: public Sprite, public GameObject {};
class VisibleGameObject : public GameObject {
protected:
Sprite m_sprite;
};
第一个VisibleGameObject类使用继承。多重继承。看起来不太好。第二个是我想要使用的,但它不允许我像这样访问Sprite的API:
VisibleGameObject man;
man.changeImage();
如何在没有继承(或代码重复)的情况下实现这一目标?
编辑:
我知道我可以使用继承或使m_sprite
成为公共成员,但我无法访问Sprite类,因为它是private
。这就是重点,问题是关于更改VisibleGameObject
的Sprite的最佳方法,遵循数据封装规则。
答案 0 :(得分:3)
我认为你仍然是“继承”的思维方式。基类应该知道要合成什么。要更改图像,您应该更改精灵实例,不应该提供组合实例的界面。例如:
class GameObject {
public:
// you can choose public access or getters and setters, it's your choice
Sprite sprite;
PhysicalBody body;
};
object = GameObject();
object.sprite = graphicalSystem.getSpriteFromImage("image.png");
// or if you prefer setters and getters
object.setSprite(sprite);
更一般地说,GameObject应该包含基类Component的实例(或指向实例的指针,取决于你的实现)。在这种情况下使用继承是有意义的,因为这样它们可以在一个存储中,如std :: map。例如:
class Component {
// ...
};
class Sprite : public Component {
//...
};
class PhysicalBody : public Component {
//...
};
class GameObject {
protected:
std::map<std::string, Component*> components;
//...
public:
Component* getComponent(const std::string& name) const;
void setComponent(const std::string& name, Component* component);
//...
};
对于主循环中的组件创建和渲染,请使用系统。例如,GraphicalSystem知道它创建的所有Sprite实例,并且渲染它时只渲染附加到某个GameObject实例的sprite。分离的组件可以被垃圾收集。有关位置和大小的信息可能是GameObject的一部分,也可能是“物理”组件。
理解它的最好方法是编写自己的原型或检查现有的实现(Artemis,Unity 3D和其他许多实现)。有关详细信息,请参阅Cowboy programming: Evolve Your Hierarchy或尝试查找实体/组件系统。
答案 1 :(得分:2)
首先,alternative for composition is private inheritance(而不是公开的),因为它们都模拟有一个关系。
重要的问题是,我们如何向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
的{{1}}公开方法,如下所示。
m_sprite
我相信这是最好的设计,特别是就封装而言。但是,与其他替代方案相比,它可能需要大量输入。
即使普通的旧C提供了暴露另一个类型接口的类型,就好像它是它自己的:指针。
实际上,假设class VisibleGameObject2: public GameObject {
public:
void changeImage() {
m_sprite.changeImage();
}
private:
Sprite m_sprite;
...
};
属于p
类型。然后,通过使用结构解除引用运算符Sprite*
,我们可以访问->
的成员(由Sprite
指向),如下所示。
p
C ++允许我们使用自定义结构解引用运算符赋予类(智能指针使用的功能)。我们的例子变成了:
p->changeImage();
和
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()
看起来像一个智能指针。这在语义上是错误的!最后,有Sutter的C ++ 11 Wrapper模式(观看他的presentation,特别是第9页的第二个slide):
VisibleGameInterface
客户以这种方式使用它:
class VisibleGameObject4 : public GameObject {
private:
Sprite m_sprite;
public:
template <typename F>
auto operator()(F f) -> decltype(f(m_sprite)) {
return f(m_sprite);
}
};
正如我们所看到的,与转发方法相比,这会将类写入的负担转移到类客户端。
答案 2 :(得分:0)
看起来您正试图直接访问Sprite的功能而不首先引用它。试试这个:
man.m_sprite.changeImage() ;
请注意,m_sprite和changeImage()应该是公共的,以便您执行此操作。否则使用公共访问器函数来操纵私有类成员。