Component/Strategy C++ 中的模式、设计和实现
Component/Strategy Pattern in C++, Design and Implementation
我有一个 C++ 项目,使用 OpenFrameworks 进行渲染,在 window 中弹跳几个球(圆圈)。它们是使用组件模式(有时称为策略模式)分别封装外观和行为来实现的。这使得外观(此处:Amiga Ball)和行为(此处:重力弹跳)在运行时可交换。
模式本身的灵感来自 Robert Nystrom's book Game Programming Patterns 中的组件模式章节。
按下一个键后,"bouncing with gravity" 的行为与 "bouncing with no-gravity" 交换(这可能是我编程而不是设计的原因)。
我想使用智能指针实现该模式(在这种情况下 unique_ptr 可以,因为没有共享,并且一个组件对象始终由 GameObject 唯一保存)。
我的代码中有几个 "ugly" 部分与 unique_ptr 的使用以及我对组件使用多态性但似乎有时必须将它们视为派生的 class 对象而不是基础-class.
我想知道它是否是一个好的和可行的设计和实现w.r.t。一般模式的设计和实现,特别是智能指针的使用。
注意:我不关心这里的常量正确性,尽管我应该关心。所以我确定那里有很多常量,但请不要 bash 我只是为了这个,除非它会影响其他问题。
所以这里...
我们有一个 class 游戏对象,其中包含屏幕上任何对象应具有的最少信息:
GameObject.hpp:
class GameObject {
public:
float x;
float y;
GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics);
GameObject();
GameObject(float x, float y);
void update();
void draw();
void setPhysicsComponent(unique_ptr<PhysicsComponent> pc);
void setGraphicsComponent(unique_ptr<GraphicsComponent> pc);
unique_ptr<PhysicsComponent>& getPhysicsComponent();
unique_ptr<GraphicsComponent>& getGraphicsComponent();
private:
unique_ptr<PhysicsComponent> physics_;
unique_ptr<GraphicsComponent> graphics_;
};
GameObject.cpp:
GameObject::GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics) : physics_(move(physics)), graphics_(move(graphics)) {}
GameObject::GameObject() {}
GameObject::GameObject(float x, float y) : x(x), y(y) {}
void GameObject::update(){
physics_->update(*this);
}
void GameObject::draw() {
graphics_->draw(*this);
}
void GameObject::setPhysicsComponent(unique_ptr<PhysicsComponent> pc) {
physics_ = std::move(pc);
}
void GameObject::setGraphicsComponent(unique_ptr<GraphicsComponent> gc) {
graphics_ = std::move(gc);
}
std::unique_ptr<GraphicsComponent>& GameObject::getGraphicsComponent() {
return graphics_;
}
std::unique_ptr<PhysicsComponent>& GameObject::getPhysicsComponent() {
return physics_;
}
然后是组件的基础 class(在这种情况下,让我们关注物理组件,因为图形组件是等效的):
class GameObject;
class PhysicsComponent {
public:
virtual ~PhysicsComponent() {std::cout<<"~PhysicsComponent()"<<std::endl;}
virtual void update(GameObject& obj) = 0;
void setXSpeed(float xs) {xSpeed = xs;};
float getXSpeed() {return xSpeed;};
void setYSpeed(float ys) {ySpeed = ys;};
float getYSpeed() {return ySpeed;};
protected:
float ySpeed;
float xSpeed;
};
PhysicsComponent 的具体实现如下所示:
class GravityBouncePhysicsComponent : public PhysicsComponent {
public:
virtual ~GravityBouncePhysicsComponent() {std::cout<<"~GravityBouncePhysicsComponent()"<<std::endl;};
GravityBouncePhysicsComponent();
GravityBouncePhysicsComponent(float radius, float xSpeed, float ySpeed, float yAccel);
virtual void update(GameObject& obj);
void setRadius(float r) {radius = r;};
float getRadius() {return radius;};
private:
// Physics
float yAcceleration;
float radius;
};
在 GravityBouncePhysicsComponent.cpp 中,空构造函数创建了这样一个组件,它具有 xSpeed 和 ySpeed(存在于基础 class 中)和 yAcceleration(仅在派生组件中有意义 [=71] =]).
然后还有一个名为 FloatBouncePhysicsComponent 的类似代码。
现在我想在主程序中使用这些class,正好是OpenFrameworks中的ofAppclass。头文件看起来像这样(省略了一些无关紧要的东西):
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
void keyPressed(int key);
// ... lots of other event functions for mouse etc.
private:
std::vector<GameObject> balls;
GameObject createAmigaBall(float x, float y);
// more variables and some methods here ...
};
现在在 ofApp.cpp 文件中它开始变得丑陋。例如,在创建一个新球的辅助函数中,它看起来像这样:
GameObject ofApp::createAmigaBall(float x, float y) {
GameObject go(x,y); // create an empty GameObject on the stack at position x,y
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
if(gravity) {
go.setPhysicsComponent(std::make_unique<GravityBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<GravityBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
} else {
go.setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<FloatBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
}
return go; // calls the copy constructor for return value, so we have no dangling pointers or refs
}
此处 AmigaBallGraphicsComponent 空构造函数生成随机半径。这个半径属于这个派生的class,因为它肯定不在每个GraphicsComponent中。
然而,这导致不得不笨拙地从生成的组件中提取半径,并访问原始指针,只是为了在 GravityBouncePhysicsComponent 中设置相同的半径。
如果您认为那很丑陋,请查看此片段:
void ofApp::keyPressed(int key){
if(key == 'a') {
if(gravity) {
for(int i=0; i<balls.size(); i++) {
unique_ptr<GravityBouncePhysicsComponent> opc (static_cast<GravityBouncePhysicsComponent*>((balls[i].getPhysicsComponent()).release()));
float r = opc->getRadius();
float xs = opc->getXSpeed();
float ys = opc->getYSpeed();
balls[i].setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>(r,xs,ys));
}
} else {
// similar code for the non-gravity case omitted ...
}
}
gravity =!gravity;
}
}
而且,好像转换为派生-classes 的东西还不够,结果证明这段代码不起作用
auto gc = std::make_unique<AmigaBallGraphicsComponent>();
go.setGraphicsComponent(gc);
虽然这个是。什么……?
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
这不应该一模一样吗?
在此先感谢您对整个混乱局面的任何见解(或者是吗?)。
我认为您的大部分问题都来自这样一个事实,即您让所有权 (std::unique_ptr
) 和类型擦除(多态性)问题从您的 API 中渗出,这需要很多不相关的生命周期管理和用户端的失望。 getGraphicsComponent
(及其伙伴)可以这样实现:
template <class DerivedGraphicsComponent = GraphicsComponent>
auto &getGraphicsComponent() {
return static_cast<DerivedGraphicsComponent &>(*graphics_);
}
这使您能够编写:
float r = go.getGraphicsComponent<AmigaBallGraphicsComponent>().getRadius();
或者省略 <AmigaBallGraphicsComponent>
并接收普通的 GraphicsComponent &
。
您确实失去了 GameObject
撤销其组件之一的所有权的能力,并且 return 通过 std::unique_ptr
取消它的所有权,但看起来您实际上并不需要它。您使用该功能的代码段最终会执行以下操作:
balls[i].setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>(r,xs,ys));
... 无论如何都会通过 graphics_
的移动赋值运算符破坏前一个组件。如果您确实需要该功能,请务必添加另一个功能:
template <class DerivedGraphicsComponent = GraphicsComponent>
auto releaseGraphicsComponent() {
return std::unique_ptr<DerivedGraphicsComponent>(
static_cast<DerivedGraphicsComponent *>(graphics_.release())
);
}
我有一个 C++ 项目,使用 OpenFrameworks 进行渲染,在 window 中弹跳几个球(圆圈)。它们是使用组件模式(有时称为策略模式)分别封装外观和行为来实现的。这使得外观(此处:Amiga Ball)和行为(此处:重力弹跳)在运行时可交换。
模式本身的灵感来自 Robert Nystrom's book Game Programming Patterns 中的组件模式章节。 按下一个键后,"bouncing with gravity" 的行为与 "bouncing with no-gravity" 交换(这可能是我编程而不是设计的原因)。 我想使用智能指针实现该模式(在这种情况下 unique_ptr 可以,因为没有共享,并且一个组件对象始终由 GameObject 唯一保存)。 我的代码中有几个 "ugly" 部分与 unique_ptr 的使用以及我对组件使用多态性但似乎有时必须将它们视为派生的 class 对象而不是基础-class.
我想知道它是否是一个好的和可行的设计和实现w.r.t。一般模式的设计和实现,特别是智能指针的使用。
注意:我不关心这里的常量正确性,尽管我应该关心。所以我确定那里有很多常量,但请不要 bash 我只是为了这个,除非它会影响其他问题。
所以这里...
我们有一个 class 游戏对象,其中包含屏幕上任何对象应具有的最少信息: GameObject.hpp:
class GameObject {
public:
float x;
float y;
GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics);
GameObject();
GameObject(float x, float y);
void update();
void draw();
void setPhysicsComponent(unique_ptr<PhysicsComponent> pc);
void setGraphicsComponent(unique_ptr<GraphicsComponent> pc);
unique_ptr<PhysicsComponent>& getPhysicsComponent();
unique_ptr<GraphicsComponent>& getGraphicsComponent();
private:
unique_ptr<PhysicsComponent> physics_;
unique_ptr<GraphicsComponent> graphics_;
};
GameObject.cpp:
GameObject::GameObject(unique_ptr<PhysicsComponent> physics, unique_ptr<GraphicsComponent> graphics) : physics_(move(physics)), graphics_(move(graphics)) {}
GameObject::GameObject() {}
GameObject::GameObject(float x, float y) : x(x), y(y) {}
void GameObject::update(){
physics_->update(*this);
}
void GameObject::draw() {
graphics_->draw(*this);
}
void GameObject::setPhysicsComponent(unique_ptr<PhysicsComponent> pc) {
physics_ = std::move(pc);
}
void GameObject::setGraphicsComponent(unique_ptr<GraphicsComponent> gc) {
graphics_ = std::move(gc);
}
std::unique_ptr<GraphicsComponent>& GameObject::getGraphicsComponent() {
return graphics_;
}
std::unique_ptr<PhysicsComponent>& GameObject::getPhysicsComponent() {
return physics_;
}
然后是组件的基础 class(在这种情况下,让我们关注物理组件,因为图形组件是等效的):
class GameObject;
class PhysicsComponent {
public:
virtual ~PhysicsComponent() {std::cout<<"~PhysicsComponent()"<<std::endl;}
virtual void update(GameObject& obj) = 0;
void setXSpeed(float xs) {xSpeed = xs;};
float getXSpeed() {return xSpeed;};
void setYSpeed(float ys) {ySpeed = ys;};
float getYSpeed() {return ySpeed;};
protected:
float ySpeed;
float xSpeed;
};
PhysicsComponent 的具体实现如下所示:
class GravityBouncePhysicsComponent : public PhysicsComponent {
public:
virtual ~GravityBouncePhysicsComponent() {std::cout<<"~GravityBouncePhysicsComponent()"<<std::endl;};
GravityBouncePhysicsComponent();
GravityBouncePhysicsComponent(float radius, float xSpeed, float ySpeed, float yAccel);
virtual void update(GameObject& obj);
void setRadius(float r) {radius = r;};
float getRadius() {return radius;};
private:
// Physics
float yAcceleration;
float radius;
};
在 GravityBouncePhysicsComponent.cpp 中,空构造函数创建了这样一个组件,它具有 xSpeed 和 ySpeed(存在于基础 class 中)和 yAcceleration(仅在派生组件中有意义 [=71] =]). 然后还有一个名为 FloatBouncePhysicsComponent 的类似代码。
现在我想在主程序中使用这些class,正好是OpenFrameworks中的ofAppclass。头文件看起来像这样(省略了一些无关紧要的东西):
class ofApp : public ofBaseApp{
public:
void setup();
void update();
void draw();
void keyPressed(int key);
// ... lots of other event functions for mouse etc.
private:
std::vector<GameObject> balls;
GameObject createAmigaBall(float x, float y);
// more variables and some methods here ...
};
现在在 ofApp.cpp 文件中它开始变得丑陋。例如,在创建一个新球的辅助函数中,它看起来像这样:
GameObject ofApp::createAmigaBall(float x, float y) {
GameObject go(x,y); // create an empty GameObject on the stack at position x,y
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
if(gravity) {
go.setPhysicsComponent(std::make_unique<GravityBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<GravityBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
} else {
go.setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>());
float r = (static_cast<AmigaBallGraphicsComponent*>((go.getGraphicsComponent()).get()))->getRadius();
(static_cast<FloatBouncePhysicsComponent*>((go.getPhysicsComponent()).get()))->setRadius(r);
}
return go; // calls the copy constructor for return value, so we have no dangling pointers or refs
}
此处 AmigaBallGraphicsComponent 空构造函数生成随机半径。这个半径属于这个派生的class,因为它肯定不在每个GraphicsComponent中。 然而,这导致不得不笨拙地从生成的组件中提取半径,并访问原始指针,只是为了在 GravityBouncePhysicsComponent 中设置相同的半径。
如果您认为那很丑陋,请查看此片段:
void ofApp::keyPressed(int key){
if(key == 'a') {
if(gravity) {
for(int i=0; i<balls.size(); i++) {
unique_ptr<GravityBouncePhysicsComponent> opc (static_cast<GravityBouncePhysicsComponent*>((balls[i].getPhysicsComponent()).release()));
float r = opc->getRadius();
float xs = opc->getXSpeed();
float ys = opc->getYSpeed();
balls[i].setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>(r,xs,ys));
}
} else {
// similar code for the non-gravity case omitted ...
}
}
gravity =!gravity;
}
}
而且,好像转换为派生-classes 的东西还不够,结果证明这段代码不起作用
auto gc = std::make_unique<AmigaBallGraphicsComponent>();
go.setGraphicsComponent(gc);
虽然这个是。什么……?
go.setGraphicsComponent(std::make_unique<AmigaBallGraphicsComponent>());
这不应该一模一样吗?
在此先感谢您对整个混乱局面的任何见解(或者是吗?)。
我认为您的大部分问题都来自这样一个事实,即您让所有权 (std::unique_ptr
) 和类型擦除(多态性)问题从您的 API 中渗出,这需要很多不相关的生命周期管理和用户端的失望。 getGraphicsComponent
(及其伙伴)可以这样实现:
template <class DerivedGraphicsComponent = GraphicsComponent>
auto &getGraphicsComponent() {
return static_cast<DerivedGraphicsComponent &>(*graphics_);
}
这使您能够编写:
float r = go.getGraphicsComponent<AmigaBallGraphicsComponent>().getRadius();
或者省略 <AmigaBallGraphicsComponent>
并接收普通的 GraphicsComponent &
。
您确实失去了 GameObject
撤销其组件之一的所有权的能力,并且 return 通过 std::unique_ptr
取消它的所有权,但看起来您实际上并不需要它。您使用该功能的代码段最终会执行以下操作:
balls[i].setPhysicsComponent(std::make_unique<FloatBouncePhysicsComponent>(r,xs,ys));
... 无论如何都会通过 graphics_
的移动赋值运算符破坏前一个组件。如果您确实需要该功能,请务必添加另一个功能:
template <class DerivedGraphicsComponent = GraphicsComponent>
auto releaseGraphicsComponent() {
return std::unique_ptr<DerivedGraphicsComponent>(
static_cast<DerivedGraphicsComponent *>(graphics_.release())
);
}