自分自身をインスタンス化できないオブジェクトに対処する最良の方法は?
-
18-09-2019 - |
質問
以前にも同様の質問をいくつかしたことがあるかと思いますが、漠然とした質問でした。これが私にとっては決して安らかにできない本当の問題だと思います。
私が扱っているのは サードパーティのライブラリ, 自身を作成できないオブジェクトがあります b2Body
. 。の b2World
しなければならない それをインスタンス化する. 。私は個人的にこのデザインパターンがあまり好きではありません。私は思います b2Body
は世界から独立して存在でき、必要に応じて世界に追加できる必要があります。とにかく巻き終わりました b2Body
自分のクラスのクラスで、 Body
, とにかく何か余分なものを追加する必要があるからです。同様に、私は World
ラッパー。今、私には 3 つの選択肢があると考えています。
- 持っている
Body
のコンストラクターはポインタを受け取りますWorld
完全にインスタンス化できるようにするため (呼び出しb2World::CreateBody
内部のどこか) -- つまり次のようなコンストラクターがありますBody *b = new Body(world_ptr)
- 合格
Body
一部の人にWorld::CreateBody
ライブラリがすでに実行している方法と同様のメソッド -- つまりBody *b = world.CreateBody(params);
- すべてのデータを複製します
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 から継承され、Body が b2Body から、World が b2World などから継承されると仮定します。
解決
私は、サードパーティのライブラリを使用するつもりなら、あなたはのああ、私はずっとそのデザインパターン好きではない。どうやら、ファクトリオブジェクトを使用して - - あなたのライブラリーは、物事のやり方がある。おそらく、実質的に、あなたのコードの複雑さを増加し、戦いを
他のヒント
b2World オブジェクトは b2Body のファクトリーであるようです。そのため、作成者は、b2Body はワールドなしでは意味がないと判断しました。
私の最初の反応は、これはインターフェイスなので、そのまま使用してください、というものです。World オブジェクトを Body のファクトリーにします。したがって、これは、パブリック コンストラクターがないこと、World オブジェクトに makeBody() メソッドがあることを除いて、アプローチ (1) に近いです。
「世界のない身体」には意味があると思いますか?もしそうなら、おそらく、Body メソッドの一部のサブセットが World なしで役立つ可能性があることがわかります。それらをどのように実装するかはわかりません。b2World なしでは存在できないため、それらは b2Body によって実装できないことは明らかです。 。したがって、可能性の 1 つは、一連の構成情報を持っているということです。
class Body {
int howBig;
String name;
Flavour flavour;
// and getter/setters
}
さて、これら(または東ではbgetters)は明らかにWorldの有無にかかわらず意味をなすことができます。
それを念頭に置くと、Body には実際には 2 つの「状態」があることがわかると思います。1 つは World に関連付けられていないとき、もう 1 つは World に関連付けられているときです。そして実際の能力は、 違う. 。したがって、実際には 2 つのインターフェイスがあります。
したがって、IndependentBody クラスと Body クラスを用意します。World Factory メソッドには署名がある可能性があります
World {
Body makeBody(IndependentBody);
}
リンクをたどってみると、 createBody
b2Body を返しませんが、 ポインタ 1 つに:
b2Body* b2World::CreateBody ( const b2BodyDef* def );
これはおそらく b2World のせいです
b2Body の有効期間を管理します (つまり, 、B2World が範囲外になったとき、または B2World 自体が削除されたときに、B2World とそれが使用するメモリを削除します)、または
B2Wsorld は b2Bodies へのポインターを維持する必要があるため、 例えば それらを反復処理して、一部の B2World 機能を実現します。
また、必要なものをすべてメモします ( b2World
) を作成するには b2Body
へのポインタです b2BodyDef
.
したがって、b2World にアタッチされていない 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 を複数のワールド、または同じワールドに複数回アタッチできることに注意してください。
b2BodyDef には実行できない操作が b2Body に対して実行できるため、(おそらくラップされた) 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 );
}
}
どこ 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 での削除の呼び出しについては省略していることに注意してください。また、クラスのスケッチでも 3 つのルールに従っていません。これらは自由に入力できます。
また、コマンドから void 以外を返す b2Body 関数を呼び出してそれらの値を返すための規定も省略しました。おそらく、ApplyTo が何らかの種類の共用体を返すようにすることで、これをカバーできます (そうする必要がある場合)。
もっと基本的なこととして、ある具体的なコマンドがその戻り値 (存在する場合) を別の具体的なコマンドにどのように提供できるかについては説明していませんでした。完全な解決策は、コマンドのベクトルではなく、 n-ary ツリー。子コマンドが最初に適用され、戻り値 (存在する場合) が親コマンドに提供されます。あなたかどうか 必要 このような複雑さについては、私には明らかに答えることができません。(コミュニティ Wiki にこの質問があったので、私はこれで報酬をもらっていないし、評判ポイントも得ていないことを考慮して、すでにかなり詳細な回答をしています。)
私はあなたが使用しているサードパーティのライブラリの設計を戦うべきではないことに同意します。そのような道を見出しことは、将来的に多くの問題を引き起こす可能性があります。
「カバーの下に」見て、ラッパーを作成することで、現在の実装の動作の仕方にサードパーティのライブラリの動作をロックダウンすることができる。
APIの将来のバージョンは同じまま、しかし根本的な意味が変化した場合どうなりますか?
突然、すべてはあなたのラッパーの観点から壊れています。
ちょうど私の0.02ます。
BOX2Dはb2Bodyオブジェクトを構築するためにbodyDefオブジェクトを使用していることを理由の一つは、複数のボディを作成するためにあなたが再利用できるように、デフです。以下のようなコード:
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オブジェクトを維持し、必要に応じてそれらから体を作成することができ、いずれかの同じループである必要はありdoes notのます。
それは理由があるのでそれを戦うしないでください。