Лучший способ справиться с объектом, который не может создать сам себя?

StackOverflow https://stackoverflow.com/questions/1482998

  •  18-09-2019
  •  | 
  •  

Вопрос

Наверное, я уже задавал несколько подобных вопросов раньше, но я ходил вокруг да около.Я думаю, что это настоящая проблема, с которой я не могу полностью покончить.

Я имею дело с сторонняя библиотека, и есть объект, который не может создать сам себя, b2Body.Тот самый b2World должен создайте его экземпляр.Лично мне не очень нравится этот шаблон дизайна;Я думаю, что b2Body должен быть способен существовать независимо от мира, а затем добавляться к миру при необходимости.Во всяком случае, я завернул b2Body с моим собственным классом class, 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 чтобы вы могли использовать его так, как хотите, а затем, после того как вы добавите его в world, он "переключится" на использование 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 и т.д.

Это было полезно?

Решение

Я думаю, если вы собираетесь использовать стороннюю библиотеку, вам следует бороться с ее дизайном только в том случае, если у вас есть гораздо более веские причины, чем о, мне не очень нравится этот шаблон дизайна.У вашей библиотеки есть способ делать что—то - по-видимому, с помощью объекта factory — и бороться с этим, что увеличит сложность вашего кода, возможно, существенно.

Другие советы

Похоже, объект b2World является фабрикой для b2Body, поэтому автор решил, что b2Body не имеет смысла без своего мира.

Моей первой реакцией было бы, что это интерфейс, так что живите с этим.Пусть объект вашего Мира станет фабрикой для вашего Тела.Таким образом, это близко к подходу (1), за исключением того, что у вас нет открытого конструктора, объект World имеет метод makeBody().

Вы думаете, что Тела без Мира имеют смысл?Если это так, возможно, вы обнаружите, что какое-то подмножество методов Body может быть полезно без World, мне не ясно, как вы их реализуете - они явно не могут быть реализованы b2Body, потому что он не может существовать без b2World.Итак, одна из возможностей заключается в том, что у вас есть набор конфигурационной информации

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

Теперь они (или, по крайней мере, bgetters) явно могли бы иметь смысл с World или без него.

Имея это в виду, я думаю, вы можете обнаружить, что на самом деле у вас есть два "состояния" Тела: одно, когда оно не связано с Миром, другое, когда оно есть.И фактические возможности таковы другой.Следовательно, у вас на самом деле есть два интерфейса.

Итак, создайте класс IndependentBody и класс Body.Метод World factory может иметь сигнатуру

World {

    Body makeBody(IndependentBody);

}

Перейдя по вашей ссылке, я вижу, что createBody возвращает не b2Body, а указатель к одному:

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

Вероятно, это связано с тем, что b2World

  1. управляет временем жизни b2Body (т. е., удаляет его и память, которую он использует, когда b2World выходит за пределы области видимости / сам удаляется), или

  2. Потому что B2Wsorld должен поддерживать указатели на b2Bodies, например , перебирать их для выполнения некоторых функций b2World.

Я также отмечаю все, что требуется (кроме b2World) для создания b2Body является указателем на b2BodyDef.

Итак, если вам нужен b2Body, который не привязан к b2World, но может быть позже присоединен к нему, почему бы не передать 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 это абстрактный базовый класс для функторов, подобный этому:

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

Обратите внимание, что я опустил некоторые детали, в основном о владении объектами и управлении памятью, а также о том, кто вызывает delete в CommandStacks;Я также не следовал правилу трех в своих набросках занятий.Вы можете заполнить их так, как вам нравится.

Я также не учел никаких условий для вызова из команды функций b2Body, которые возвращают что-то отличное от void, и возвращают эти значения;вероятно, вы можете покрыть это (если вам нужно), выполнив applyTo возврат какого-либо объединения.

Что еще более важно, я не описал, как одна конкретная команда может передать свое возвращаемое значение (если таковое имеется) другой конкретной команде.Полное решение состояло бы в том, чтобы иметь не Вектор команд, а n-арное дерево из них, где дочерние команды применяются первыми, а их возвращаемые значения (если таковые имеются) передаются их родительской команде.Являетесь ли вы потребность такая сложность - это вопрос, на который я, очевидно, не могу ответить.(И я уже дал довольно подробный ответ, учитывая, что мне за это не платят и я не получаю очков репутации, поскольку вы, сообщество Wiki, задали этот вопрос.)

Я согласен, что вам не следует бороться с дизайном сторонней библиотеки, которую вы используете.Движение по такому пути может вызвать множество проблем в будущем.

Заглядывая "под прикрытие" и создавая оболочки, вы, возможно, привязываете поведение сторонней библиотеки к тому, как ведет себя текущая реализация.

Что произойдет, если будущая версия API останется прежней, но базовая семантика изменится?

Внезапно все рушится с точки зрения вашей обертки.

Всего лишь мои 0,02.

Одна из причин, по которой 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 в качестве метаданных и создавать тела из них по мере необходимости.

Не боритесь с этим, потому что это происходит по какой-то причине.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top