我想我以前曾问过几个类似的问题,但我在丛林中殴打。我认为这是我无法安息的真正问题。
我正在处理third party library,并且有一个无法自行创建的对象b2Body
。 b2World
必须instantiate it。我个人不太喜欢这种设计模式;我认为b2Body
应该能够独立于世界而存在,然后在需要时被添加到世界。无论如何,我用我自己的班级b2Body
包裹Body
,因为无论如何我需要添加一些额外的东西。同样,我有一个World
包装器。现在我想我有3个选择:
Body
的构造函数获取指向World
的指针,以便可以完全实例化(在内部的某处调用b2World::CreateBody
) - 即具有Body *b = new Body(world_ptr)
之类的构造函数Body
传递给某些World::CreateBody
方法,例如图书库已经如何处理 - 即Body *b = world.CreateBody(params);
b2Body
中的所有数据,以便您可以随意使用它,然后在将其添加到世界后,它将“切换”以使用b2Body
数据 - 即Body b
以及之后world.addBody(b)
。(1)和(2)意味着你不能拥有Body
没有World
,我可能不需要它,但是拥有那个选项可能会很好[所以我可以将它用作其他对象的模板等。不确定有什么其他优点和缺点。 (3)看起来更好,但实现起来要做的工作要多得多,这意味着我必须复制已经包含在b2Body
中的大部分数据。
你有什么想法?我CW
这只是没有人担心。
我仍然无法安息。这就是每个选项的样子:
选项1:(我更喜欢)
World w;
Body b;
Fixture f;
b.addFixture(f);
w.addBody(b);
选项2:(位于中间某处)
World w;
Body b(w);
Fixture f(b);
选项3:(Box2D如何做)
World *w = new World;
Body *b = w->CreateBody(args);
Fixture *f = b->CreateFixture(args);
选项2和3并没有那么不同,但它改变了谁可以控制创建对象。
我如何实际实施选项3? World::CreateBody()
必须致电b2World::CreateBody(args)
,调用b2Body::b2Body()
并返回b2Body
,但不会调用Body::Body(args)
这是一个问题。 b2Body
将完全初始化,但我的包装器没有地方可以做到这一点......更具体地说,我将如何编写World::CreateBody(const BodyDef &bd)
?假设BodyDef继承自b2BodyDef,Body来自b2Body,World来自b2World等。
答案 0 :(得分:6)
我认为,如果你打算使用第三方库,你应该只在你的设计上打架,如果你有更好的理由而不是哦,我不喜欢那种设计模式。你的图书馆有一种做事方式 - 显然,通过使用工厂对象 - 并且可能会大大增加你的代码复杂性。
答案 1 :(得分:2)
听起来b2World对象是b2Body的工厂,所以作者已经决定b2Body在没有世界的情况下没有任何意义。
我的第一反应是这是界面,所以忍受它。让您的World对象成为您身体的工厂。因为除了你没有公共构造函数之外,接近于方法(1),World对象有一个makeBody()方法。
你认为没有世界的身体有意义吗?如果是这样,也许你发现Body方法的某些子集在没有World的情况下可能有用,我不清楚你是如何实现它们的 - 它们显然不能被b2Body实现,因为没有b2World它就不能存在。所以有一种可能性就是你有一套配置信息
class Body {
int howBig;
String name;
Flavour flavour;
// and getter/setters
}
现在,无论有没有世界,这些(或东部的bgetters)显然都有意义。
考虑到这一点,我想你可能会发现你实际上有两个“状态”的身体,一个与世界无关,一个与世界无关。实际功能不同。因此,您实际上有两个接口。
所以有一个IndependentBody类和一个Body类。 World工厂方法可能有签名
World {
Body makeBody(IndependentBody);
}
答案 2 :(得分:1)
我同意您不应该与您正在使用的第三方库的设计作斗争。沿着这样的道路前进可能会在未来引发很多问题。
通过查看“在幕后”并创建包装器,您可能会将第三方库的行为锁定为当前实现的行为方式。
如果API的未来版本保持不变,但基础语义会发生变化,会发生什么?
突然间,从包装纸的角度来看,一切都已破裂。
只是我的0.02。
答案 3 :(得分:1)
根据您的链接,我看到createBody
没有返回b2Body,而是指针返回一个:
b2Body* b2World::CreateBody ( const b2BodyDef* def );
这可能是因为b2World
管理b2Body的生命周期(即,删除它以及当B2World超出范围/它本身被删除时使用的内存),或者
因为B2Wsorld需要维护指向b2Bodies的指针,例如迭代它们以完成某些B2World功能。
我还注意到创建b2World
所需的全部内容(b2Body
除外)是指向b2BodyDef
的指针。
所以如果你想要一个没有附加到b2World的b2Body,但是稍后可以附加到一个b2Body,为什么不绕过b2BodyDefs或指向它们?
我可能为b2BodyDef创建一个瘦包装器,例如,:
class b2BodyDefWrapper {
public const b2BodyDef& b2bodyDef;
public b2BodyDefWrapper( const b2BodyDef& bodydef ) : b2bodyDef(bodydef) {}
public const b2Body* reifyOn( b2World& world) const {
return world.CreateBody( b2bodyDef ) ;
}
}
请注意,我可以将此b2BodyDefWrapper附加到多个世界,或多次连接到同一个世界。
现在可能你可以对b2Body做一些你无法对b2BodyDef做的事情,所以传递(可能包裹)b2BodyDefs将不适合你的目的。在这种情况下,我可能会使用命令模式将一个函数列表“附加”到b2BodyDefWrapper
,这将在每个具体化的b2Body上“重放”:
class b2BodyDefWrapper {
private std::vector<Command&> commandStack;
public const b2BodyDef& b2bodyDef;
public b2BodyDefWrapper( const b2BodyDef& bodydef ) : b2bodyDef(bodydef) {}
public const b2Body* reify( b2World& world) const {
b2body* ret = world.CreateBody( &b2bodyDef ) ;
for (int i=0; i< commandStack.size(); i++) {
v[i].applyTo( ret ) ;
}
return ret;
}
public void addCommand( const Command& command ) {
commandStack.push_back( command );
}
}
其中Command
是Functors的抽象基类,如下所示:
class Command {
virtual ~Command() {}
virtual void applyTo( b2Body* body ) = 0 ;
}
具体的子类:
class ApplyForce : public Command {
private const b2Vec2& force;
private const b2Vec2& point;
ApplyForce(const b2Vec2& f, const b2Vec2& p) : force(f), point(p) {}
virtual void applyTo( b2Body* body ) {
body->ApplyForce( force, point ) ;
}
}
然后我可以像这样使用我的包装器:
extern b2BodyDef& makeb2BodyDef();
b2BodyDefWrapper w( makeb2BodyDef() ) ;
ApplyForce a( ..., ... );
w.addCommand( a ) ;
...
b2World myworld;
b2World hisWorld;
w.reifyOn( myWorld ) ;
w.reifyOn( hisWorld) ;
请注意,我遗漏了一些细节,主要是关于对象所有权和内存管理,以及谁在CommandStack上调用delete;在我的课程草图中,我也没有遵循三法则。您可以随意填写这些内容。
我也省略了从命令中调用b2Body函数的任何规定,这些函数返回除void之外的函数并返回这些值;你可以通过让ApplyTo返回某种类型的联合来覆盖这个(如果你需要)。
更基本的是,我没有介绍一个具体的Command如何将其返回值(如果有的话)提供给另一个具体的Command。一个完整的解决方案是没有Vector of Commands,而是一个 n -ary树,其中首先应用子命令,并将它们的返回值(如果有的话)提供给它们的父命令。无论你需要这样的复杂性是一个我显然无法回答的问题。 (我已经给出了一个非常详细的答案,认为我既没有得到报酬,也没有得到声誉点,因为你社区维基这个问题。)
答案 4 :(得分:1)
box2D使用bodyDef对象构造b2Body对象的一个原因是,您可以重新使用def来创建多个实体。代码如:
b2BodyDef myDef;
// fill out def
for (int i=0; i < 100; ++i) {
for (int j=0; j < 100; ++j) {
myDef.x = i;
myDef.y = j
b2Body* body = world.CreateBody(myDef)
}
}
是一种非常有效且紧凑的方法,可以创建具有相同特征的许多对象。它也不必在同一个循环中,你可以将def对象保持为元数据,并根据需要从它们创建实体。
不要打它,因为它是有原因的。