处理无法实例化的对象的最佳方法?

时间:2009-09-27 07:17:43

标签: c++ design-patterns qt box2d

我想我以前曾问过几个类似的问题,但我在丛林中殴打。我认为这是我无法安息的真正问题。

我正在处理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)
  4. (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等。

5 个答案:

答案 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

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

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

  3. 我还注意到创建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对象保持为元数据,并根据需要从它们创建实体。

不要打它,因为它是有原因的。