Question

Je suppose que j'ai posé quelques questions semblables, mais je battais autour du pot. Je pense que c'est le vrai problème que je ne peux pas tout à fait à son dernier repos.

Je fais face à un bibliothèque tiers , et il y a un objet qui ne peut pas créer lui-même , b2Body. Le b2World doit instancier . Personnellement, je n'aime pas ce modèle de conception très bien; Je pense que le b2Body devrait être en mesure d'exister indépendamment du monde, puis ajouter au monde en cas de besoin. Quoi qu'il en soit, je l'ai enveloppé b2Body avec ma propre classe de classe, Body, parce que je dois ajouter des choses supplémentaires à toute façon. De même, j'ai une enveloppe World. Maintenant, je me dis que j'ai 3 options:

  1. Avoir le constructeur de Body prend un pointeur vers World afin qu'il puisse être entièrement instancié (appels b2World::CreateBody quelque part à l'intérieur) - à savoir avoir un constructeur comme Body *b = new Body(world_ptr)
  2. Passe Body à une méthode de World::CreateBody comme la façon dont la bibliothèque fait déjà - à savoir Body *b = world.CreateBody(params);
  3. Dupliquer toutes les données b2Body afin que vous puissiez l'utiliser comme vous le voulez, puis après avoir ajouté au monde, il sera « basculer » d'utiliser les données de b2Body -. C.-à-Body b et world.addBody(b) plus tard

(1) et (2) signifie que vous ne pouvez pas avoir un Body sans World, que je vais probablement pas besoin, mais il pourrait être agréable d'avoir cette option [afin que je puisse l'utiliser comme modèle pour d'autres objets et autres]. Je ne sais pas ce que les autres avantages et les inconvénients, il y a. (3) semble plus agréable, mais il est beaucoup plus de travail à mettre en œuvre, et cela signifie que je dois dupliquer la plupart des données qui est déjà contenu dans b2Body.

Que pensez-vous? Je CW ce juste pour que personne ne frets.


Je ne peux toujours pas poser ce pour se reposer. C'est ce que chacune des options ressemblerait à ceci:

Option 1: (ce que je préfère)

World w;
Body b;
Fixture f;
b.addFixture(f);
w.addBody(b);

Option 2: (quelque part au milieu)

World w;
Body b(w);
Fixture f(b);

Option 3: (comment le fait Box2D)

World *w = new World;
Body *b = w->CreateBody(args);
Fixture *f = b->CreateFixture(args);

Options 2 et 3 ne sont pas si différents, mais il change qui a le contrôle crée les objets.

Comment puis-je réellement mettre en œuvre l'option 3 si? World::CreateBody() doit appeler b2World::CreateBody(args) qui appelle b2Body::b2Body() et retourne b2Body mais jamais appelle Body::Body(args) qui est un problème. Le b2Body obtiendrait complètement initialisé, mais mon emballage n'a pas sa place pour le faire est chose ... Plus précisément, comment pourrais-je écrire World::CreateBody(const BodyDef &bd)? En supposant BodyDef hérité de b2BodyDef, du corps de b2Body Monde de b2World, etc.

Était-ce utile?

La solution

Je pense que, si vous allez utiliser une bibliothèque tierce, vous ne devriez combattre sa conception si vous avez une raison beaucoup mieux que oh, je ne aime pas ce modèle de conception beaucoup . Votre bibliothèque a une façon de faire les choses - apparemment, à l'aide d'un objet usine -. Et le combat qui augmentera la complexité de votre code, peut-être pas

Autres conseils

Sons comme l'objet b2World est une usine de b2Body, de sorte que l'auteur a décidé qu'un b2Body n'a pas de sens sans son monde.

Ma première réaction serait que ce soit l'interface, donc vivre avec elle. Demandez à votre objet du monde soit une usine pour votre corps. Alors, qui est proche de l'approche (1), sauf que vous ne disposez pas d'un constructeur public, l'objet du monde a une méthode makeBody ().

Vous pensez que les organismes sans monde sens? Si oui, peut-être ce que vous trouvez est que certains sous-ensemble de méthodes de corps pourrait être utile sans monde, je ne suis pas clair comment vous les mettre en œuvre - ils ne peuvent pas être mises en œuvre par b2Body, parce qu'il ne peut pas exister clairement sans b2World . Donc, une possibilité est que vous avez un ensemble d'informations config

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

Maintenant ces (ou à l'est du bgetters) pourrait bien donner un sens avec ou sans monde.

Avec cela à l'esprit, je pense que vous pouvez constater que vous avez deux « états actualy » du corps, l'un quand il est pas associé à du monde, l'un quand il est. Et les capacités réelles sont différents . Par conséquent, vous avez en fait deux interfaces.

une classe IndependentBody et une classe de corps. La méthode de fabrication du monde pourrait avoir une signature

World {

    Body makeBody(IndependentBody);

}

Après votre lien, je vois que createBody ne retourne pas b2Body, mais pointeur à l'un:

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

Il est probable que b2World

  1. gère la durée de vie de b2Body ( i.e.. , supprime et la mémoire qu'il utilise lorsque le B2World est hors de portée / est lui-même supprimé), ou

  2. Parce que le B2Wsorld a besoin de maintenir des pointeurs vers b2Bodies, par exemple. pour itérer sur eux pour accomplir certaines fonctionnalités de B2World.

Je note aussi tout ce qui est nécessaire (autre qu'un b2World) pour créer un b2Body est un pointeur vers une b2BodyDef.

Donc, si vous voulez un b2Body qui n'est pas attaché à un b2World, mais peut à un plus tard, être attaché à un, pourquoi pas passer autour b2BodyDefs, ou des pointeurs vers eux?

.

peut créer une enveloppe mince pour un b2BodyDef, par exemple ,

 class b2BodyDefWrapper {
   public const b2BodyDef& b2bodyDef;
   public b2BodyDefWrapper( const b2BodyDef& bodydef ) : b2bodyDef(bodydef) {}
   public const b2Body* reifyOn( b2World& world) const { 
     return world.CreateBody( b2bodyDef ) ;
   }
 }

Notez que je pourrais joindre ce b2BodyDefWrapper à plusieurs mondes, ou le même monde plus d'une fois.

Maintenant, il se peut que vous pouvez faire des choses à un b2Body que vous ne pouvez pas faire un b2BodyDef, et ainsi que le passage autour de (peut-être enveloppé) b2BodyDefs ne conviennent pas à vos besoins. Dans ce cas, je pourrais utiliser le modèle de commande pour « attacher » une liste des fonctions à l'b2BodyDefWrapper, ce serait « rejoué » sur chaque b2Body réifiée:

 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 est une classe de base abstraite pour Foncteurs, comme ceci:

  class Command {
     virtual ~Command() {}
     virtual void applyTo( b2Body* body ) = 0 ;
  }

avec des sous-classes concrètes:

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

Je pourrais utiliser mon emballage comme ceci:

extern b2BodyDef& makeb2BodyDef();
b2BodyDefWrapper w( makeb2BodyDef()  ) ; 
ApplyForce a( ..., ... );
w.addCommand( a ) ;
...
b2World myworld;
b2World hisWorld;
w.reifyOn( myWorld ) ;
w.reifyOn( hisWorld) ;

Notez que j'ai omis quelques détails, principalement sur la propriété de l'objet et de la gestion de la mémoire, et qui appelle à supprimer CommandStacks; J'ai pas suivi la règle de trois dans mes croquis des classes. Vous pouvez remplir ces que vous le souhaitez.

Je l'ai également laissé toute disposition pour appeler, à partir d'une commande, les fonctions qui retournent b2Body autre que vide et le retour de ces valeurs; vous pouvez probablement couvrir ce (si vous devez) en ayant ApplyTo retourner une union de quelque sorte.

Plus fondamentalement, je n'ai pas couvert comment une commande concrète peut fournir sa valeur de retour (le cas échéant) à une autre commande de béton. Une solution complète serait pas un vecteur de commandes, mais un n arbre -aire d'entre eux, où l'enfant Les commandes sont appliquées d'abord, et leurs valeurs de retour (le cas échéant) sont fournis à leur commande parent . Que vous besoin une telle complexité est une question que je ne peux évidemment pas répondre. (Et je l'ai déjà donné une réponse assez détaillée, je ne considérais payé pour cela, ni que je reçois point de réputation, puisque vous Communauté Wiki'd cette question.)

Je suis d'accord que vous ne devriez pas combattez la conception d'une bibliothèque 3ème partie que vous utilisez. En descendant un tel chemin peut causer beaucoup de problèmes à l'avenir.

En regardant « sous les couvertures » et la création d'emballages, vous pouvez être sur le comportement de verrouillage de la bibliothèque 3e partie à la manière dont la mise en œuvre actuelle se comporte.

Qu'est-ce qui se passera si une future version de l'API reste le même, mais la sémantique sous-jacente changer?

Tout à coup, tout est rompu du point de vue de votre emballage.

Juste mon 0,02.

Une raison pour laquelle Box2D utilise des objets bodyDef pour construire des objets b2Body est que vous pouvez réutiliser la définition pour créer plusieurs corps. Code comme:

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

est un moyen très efficace et compact de créer de nombreux objets avec les mêmes caractéristiques. Il n'a pas besoin d'être dans la même boucle soit, vous pouvez garder les objets autour def sous forme de métadonnées, et créer des organismes d'eux au besoin.

Ne pas combattre parce qu'il est là pour une raison.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top