2

应该如何处理组合而不是继承?考虑以下类:

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. 这就是重点,问题是关于更改 aVisibleGameObject的 Sprite 的最佳方式,遵循数据封装的规则。

4

3 回答 3

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);
    //...
};

对于主循环中的组件创建和渲染,请使用Systems。例如,GraphicalSystem 知道它创建的所有 Sprite 实例,并且在渲染时它只渲染附加到某个 GameObject 实例的精灵。分离的组件可以被垃圾收集。有关位置和大小的信息可能是游戏对象的一部分,也可能是“物理”组件。

理解它的最好方法是编写自己的原型或检查现有的实现(ArtemisUnity 3D和许多其他)。有关更多信息,请参阅Cowboy 编程:发展您的层次结构或尝试查找实体/组件系统。

于 2013-03-24T09:13:15.750 回答
2

首先,组合的替代方案是私有继承(而不是公共继承),因为两者都建立了具有关系的模型。

重要的问题是我们如何将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();

这种方法虽然方便,但也有很多缺陷:

  1. 至于公共继承,这种方法不能很好地控制Sprite应该公开哪些公共成员。
  2. 它仅适用于一个成员(也就是说,您不能使用相同的技巧来公开两个成员接口)。
  3. 它与界面混淆。确实,例如考虑VisualGameObject有一个方法doSomething()v然后,在一个应该做的对象上调用这个方法,v.doSomething()而调用changeImage()一个应该使用v->changeImage(). 这令人困惑。
  4. 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(); } );

正如我们所看到的,与转发方法相比,这种方法将打字的负担从类编写者转移到了类客户端。

于 2013-03-24T22:01:47.463 回答
0

看起来您正在尝试直接访问 Sprite 的函数而不先引用它。尝试这个:

man.m_sprite.changeImage() ;

请注意, m_sprite 和 changeImage() 应该是公开的,以便您执行此操作。否则使用公共访问器函数来操作私有类成员。

于 2013-03-22T04:03:41.643 回答