C++ 构图模式
如何处理组合而不是继承?考虑下面的类:C++ 构图模式,c++,design-patterns,inheritance,encapsulation,composition,C++,Design Patterns,Inheritance,Encapsulation,Composition,如何处理组合而不是继承?考虑下面的类: class GameObject {...}; class Sprite { public: void changeImage(...); }; class VisibleGameObject: public Sprite, public GameObject {}; class VisibleGameObject : public GameObject { protected: Sprite m_sprite; };
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类,因为它是私有的。这就是问题所在,问题是关于按照数据封装规则更改VisibleGameObject
精灵的最佳方法。看起来您正在尝试直接访问精灵的功能,而不首先引用它。试试这个:
man.m_sprite.changeImage() ;
请注意,m_sprite和changeImage()应该是公共的。否则,请使用公共访问器函数来操作私有类成员。我认为您仍然需要“组合优先于继承”的思维方式。基类应该知道要合成什么。若要更改图像,您应该更改sprite实例,您不应该提供组合实例的接口。例如:
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);
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);
//...
};
更一般地说,游戏对象应该包含基类组件的实例(或指向实例的指针,具体取决于您的实现)。在这种情况下使用继承是有意义的,因为这样它们就可以放在一个存储中,比如std::map。例如:
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);
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);
//...
};
类组件{
// ...
};
类Sprite:公共组件{
//...
};
类PhysicalBody:公共组件{
//...
};
类游戏对象{
受保护的:
地图组件;
//...
公众:
Component*getComponent(const std::string&name)const;
void setComponent(const std::string&name,Component*Component);
//...
};
对于主循环中的组件创建和渲染,请使用系统。例如,GraphicalSystem知道它创建的精灵的所有实例,在渲染时,它只渲染附加到某个游戏对象实例的精灵。可以对分离的组件进行垃圾回收。有关位置和大小的信息可能是游戏对象的一部分,也可能是“物理”组件
理解它的最好方法是编写自己的原型或检查现有的实现(,以及其他许多实现)。有关更多信息,请参阅或尝试查找实体/组件系统。首先,由于模型a和模型a之间都有-a关系,因此为实体/组件系统(而非公共系统)
重要的问题是我们如何将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
公共方法转发调用到mu sprite
,如下所示
class VisibleGameObject2: public GameObject {
public:
void changeImage() {
m_sprite.changeImage();
}
private:
Sprite m_sprite;
...
};
我相信这是最好的设计,尤其是在封装方面。但是,与其他备选方案相比,它可能需要大量键入
结构解引用算子
即使是普通的旧C也提供了公开另一个类型的接口的类型,就好像它是自己的:指针
实际上,假设p
属于Sprite*
类型。然后,通过使用结构解除引用操作符->
,我们可以访问Sprite
(由p
指向)的成员,如下所示
p->changeImage();
C++允许我们为类赋予定制的结构取消引用操作符(智能指针很好地使用了这一特性)。我们的例子是:
class VisibleGameObject3 : public GameObject {
public:
Sprite* operator ->() {
return &m_sprite;
}
private:
Sprite m_sprite;
...
};
及
这种方法虽然方便,但有许多缺陷:
至于公共继承,这种方法不能很好地控制哪些Sprite
公共成员应该被公开
它只对一个成员有效(也就是说,不能使用相同的技巧公开两个成员接口)
它把界面弄乱了。事实上,例如,考虑<代码> VisualGalaseObjult/Cuth>有一个方法:代码> DOMMETHONSE()/<代码>。然后,要对对象调用此方法,应该执行v.doSomething()
,而要调用changeImage()
应该使用v->changeImage()
。这令人困惑
它使VisibleGameInterface
看起来像一个智能指针。这在语义上是错误的李>
C++11包装器模式
最后,还有Sutter的C++11包装模式(看看他的,特别是第9页的第二页):
正如我们所看到的,与转发方法相比,这种方法将键入的负担从类编写器转移到类客户端。您无法访问Sprite这样的方法。只能通过Sprite的对象调用Sprite的方法。另外,您希望将changeImange()public
,以便派生类可以访问它们。我认为friend
不会有太大变化。至于OP的问题,它不能。您要么求助于多重继承(这并不一定总是坏的),要么自己重新编写包装器方法。@Nbr44,我忽略了最后几行,因此我的评论不正确。对不起
VisibleGameObject4 v4;
v4( [](Sprite& s) { return s.changeImage(); } );