2009-09-27 86 views
2

我猜我以前曾经问过几个类似的问题,但我在丛林中跳动。我认为这是我不能放松的真正问题。处理无法实例化对象的最佳方法?

我正在处理一个third party library,有一个对象无法创建自己b2Bodyb2World必须instantiate it。我个人不太喜欢这种设计模式;我认为b2Body应该能够独立于世界而存在,然后在需要时加入世界。无论如何,我已将b2Body与我自己的班级Body包装在一起,因为无论如何我都需要添加一些额外的东西。同样,我有一个World包装。现在,我想我有3种选择:

  1. 有无Body的构造函数需要一个指针来World,以便它可以被完全实例化(调用b2World::CreateBody里面的地方) - 即有像Body *b = new Body(world_ptr)
  2. Body构造一些World::CreateBody方法就像库已经这样做 - 即Body *b = world.CreateBody(params);
  3. 复制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,来自b2Body的Body,来自b2World的World等等。

+4

我认为这是一个完全合法的设计问题,不需要CW。 – djna 2009-09-27 07:52:39

+0

哦,好吧......我比答案更感兴趣的答案......我忘了答案也无法获得代表。哎呀。 – mpen 2009-09-27 18:03:41

回答

6

我想,如果你要使用第三方库,如果你有超过哦,我不一个更好的原因,你应该只打它的设计不喜欢那种设计模式很多。你的图书馆有办法做事 - 显然,通过使用工厂对象 - 以及可能大幅增加代码复杂性的战斗。

+0

呃......比这稍微多了一点。我使用Qt和Box2D,而Qt按照我的建议来完成。这将保持整个项目的一致性。但通过采用Box2D的做法,突然间我有两种不同的设计模式。我想这不是什么大事,我不应该拒绝。 – mpen 2009-09-27 18:05:48

2

听起来像b2World对象是b2Body的工厂,所以作者已经决定了b2Body在没有它的世界的情况下没有意义。

我的第一反应是,这是接口,所以住它。让你的世界对象成为你的身体的工厂。因此,除了没有公共构造函数之外,接近方法(1)的World对象具有makeBody()方法。

你认为没有世界的身体是有意义的吗?如果是这样,或许你发现Body方法的一些子集在没有World的情况下可能是有用的,我不清楚你是如何实现它们的 - 他们显然不能被b2Body实现,因为如果没有b2World,他不可能存在。所以,一种可能性是,你有一组的配置信息

class Body { 
     int howBig; 
     String name; 
     Flavour flavour; 
     // and getter/setters 
} 

现在这些(或东bgetters)显然可以使感觉有或没有世界。考虑到这一点,我想你可能会发现你实际上有Body的两个“状态”,一个与世界无关,一个是时。实际能力是不同。因此你实际上有两个接口。

所以有一个IndependentBody类和一个Body类。世界工厂方法可能有一个签名

World { 

    Body makeBody(IndependentBody); 

} 
+0

我认为这就像(2)一样。 “IndependentBody”就像Box2D的b2BodyDef。我希望删除一层抽象。 – mpen 2009-09-27 18:12:56

1

我同意你不应该打击你正在使用的第三方库的设计。走向这条道路可能会在未来造成很多问题。

通过查看“底下”并创建包装器,您可能会锁定第三方库的行为,使其与当前实现的行为方式相关。

如果未来版本的API保持不变,但基础语义更改会发生什么?

突然,从包装的角度来看,一切都被打破了。

只是我的0.02。

1

按照你的链接,我看到createBody不返回b2Body,但指针一个:

b2Body* b2World::CreateBody (const b2BodyDef* def);  

这可能是因为b2World

  1. 管理b2Body寿命(,删除它和它在B2World超出范围时使用的内存/本身被删除)或

  2. 因为B2Wsorld需要保持指向b2Body的指针,例如来遍历它们来完成一些B2World功能。

我也注意到了所有的需要(除b2World)创建b2Body是一个指向b2BodyDef

所以,如果你想要一个b2Body不附加到b2World,但可以在稍后被附加到一个,为什么不传递b2BodyDefs或指向他们?

I 可能为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是仿函数的抽象基类,像这样:

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) ; 

请注意,我省略了一些细节,主要是关于对象所有权和内存管理,以及谁在CommandStacks上调用delete;我在这些课程的草图中也没有遵循三的规则。你可以随意填写。

我也忽略了任何规定来调用,从一个命令,b2Body函数返回非空值并返回这些值;你可以通过ApplyTo返回某种联合来覆盖这个(如果你需要的话)。

更重要的是,我没有介绍一个具体的Command如何将其返回值(如果有)提供给另一个具体的Command。一个完整的解决方案应该是没有一个命令矢量,但是它们的一个树,其中首先应用子命令,并且它们的返回值(如果有的话)被提供给它们的父命令。无论你是需要这样的复杂性是一个我显然无法回答的问题。 (我已经给出了一个非常详细的答案,认为我既没有为此付费,也没有获得声望点,因为你的社区维基是这个问题。)

+0

嗯,它是#2。其余的我将不得不吸收......我不确定我对你的命令结构有什么看法。我试图简化的东西:) – mpen 2009-09-27 18:30:11

1

box2D使用bodyDef对象的一个​​原因构造b2Body对象是为了让您可以重新使用def来创建多个body。代码如:

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对象保留为元数据,并根据需要从它们创建主体。

不要因为它出于某种原因而与之对抗。

+0

你可以做到这一点没有身体防御,只是通过克隆和修改一些属性。 – mpen 2009-10-08 23:08:20

+0

不知道这是否属实。b2Body必须存在于一个世界中,因此克隆它并修改属性可能会在身体被克隆的世界内产生副作用。它可能会在原始位置产生碰撞/接触事件,而不是在属性设置后的位置? 此外,这并没有解决作为一个模板保持周围。将一个实际的b2Body作为一个模板来克隆从而变得不那么有意义。它在什么世界?如果有什么东西碰撞它会怎么样?它在模拟吗? – 2009-10-12 16:26:27

+0

好吧,我建议你在这种情况下根本不会把它添加到世界上。这恰恰是一个拥有一个不属于一个世界的身体的情况。另外,假设没有任何多线程shannanigans正在进行,可能你会在下一个物理步骤运行之前完成更新,因此碰撞不会成为问题。 – mpen 2009-10-16 06:23:52