2

我想我以前问过一些类似的问题,但我是在拐弯抹角。我认为这是我不能完全休息的真正问题。

我正在处理第三方库,并且有一个无法自行创建的对象b2Bodyb2World必须实例化它。我个人不是很喜欢这种设计模式;我认为b2Body应该能够独立于世界而存在,然后在需要时添加到世界中。无论如何,我已经b2Body用我自己的类包裹了Body,因为无论如何我都需要向它添加一些额外的东西。同样,我有一个World包装器。现在我想我有 3 个选项:

  1. HaveBody的构造函数需要一个指针,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没有 a 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)which 调用b2Body::b2Body()并返回b2Body但从不调用Body::Body(args)which 是一个问题。将b2Body被完全初始化,但我的包装器没有地方做它的事情......更具体地说,我将如何写World::CreateBody(const BodyDef &bd)?假设 BodyDef 继承自 b2BodyDef,Body 继承自 b2Body,World 继承自 b2World,等等。

4

5 回答 5

6

我认为,如果您要使用第三方库,则只有在有更好的理由时才应该反对它的设计,哦,我不太喜欢那种设计模式。你的库有一种做事的方式——显然,通过使用工厂对象——并且与它作斗争会增加你的代码复杂性,可能会大大增加。

于 2009-09-27T08:07:50.943 回答
2

听起来 b2World 对象是 b2Body 的工厂,所以作者认为 b2Body 没有它的世界就没有意义。

我的第一反应是,这是界面,所以请接受它。让您的 World 对象成为您的 Body 的工厂。所以这接近方法(1),除了你没有公共构造函数,World 对象有一个 makeBody() 方法。

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

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

现在,无论有没有 World,这些(或东部的 bgetters)显然都有意义。

考虑到这一点,我想你可能会发现你实际上有两种身体“状态”,一种与世界无关,一种与世界相关。和实际能力不同。因此,您实际上有两个接口。

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

World {

    Body makeBody(IndependentBody);

}
于 2009-09-27T08:01:44.930 回答
1

我同意您不应该与您正在使用的 3rd 方库的设计作斗争。沿着这样的道路前进可能会在未来引起很多问题。

通过查看“幕后”并创建包装器,您可能会将 3rd 方库的行为锁定为当前实现的行为方式。

如果 API 的未来版本保持不变,但底层语义发生变化,会发生什么?

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

只是我的0.02。

于 2009-09-27T09:02:42.487 回答
1

按照您的链接,我看到它createBody不会返回 b2Body,而是指向一个的指针

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

这可能是因为 b2World

  1. 管理 b2Body 生命周期(,当 B2World 超出范围/被删除时,删除它和它使用的内存),或者

  2. 因为 B2Wsorld 需要维护指向 b2Bodies 的指针,例如迭代它们以完成一些 B2World 功能。

b2World我还注意到创建 a所需的所有内容(除了 a )b2Body是指向 a 的指针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 做的事情,因此传递(可能是包装的)b2BodyDef 将不适合您的目的。在这种情况下,我可能会使用命令模式将函数列表“附加”到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 );
   }
 }

CommandFunctor 的抽象基类在哪里,如下所示:

  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;在我的课堂草图中,我也没有遵循三原则。您可以根据需要填写这些内容。

我还省略了从命令调用返回 void 以外的 b2Body 函数并返回这些值的任何规定;您可以通过让 ApplyTo 返回某种联合来解决这个问题(如果需要)。

更根本的是,我没有讨论一个具体的命令如何将其返回值(如果有)提供给另一个具体的命令。一个完整的解决方案是不使用命令向量,而是使用它们的n叉树,其中首先应用子命令,并将它们的返回值(如果有)提供给它们的父命令。您是否需要这种复杂性是我显然无法回答的问题。(而且我已经给出了一个非常详细的答案,考虑到我既没有为此获得报酬,也没有获得声誉点,因为您在社区维基上提出了这个问题。)

于 2009-09-27T09:32:40.913 回答
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 对象作为元数据保留,并根据需要从它们创建主体。

不要抗拒它,因为它的存在是有原因的。

于 2009-10-08T17:38:32.473 回答