2つのクラス間の多対多の関係を表す良い方法は何ですか?


10

AとBの2つのオブジェクトタイプがあるとします。これらのオブジェクト間の関係は多対多ですが、どちらも他方の所有者ではありません。

AインスタンスとBインスタンスの両方が接続を認識する必要があります。片道だけではありません。

だから、これを行うことができます:

class A
{
    ...

    private: std::vector<B *> Bs;
}

class B
{
    private: std::vector<A *> As;
}

私の質問は次のとおりです。接続を作成および破棄する関数をどこに配置すればよいですか。

A :: Attach(B)である必要があります。これにより、A :: BsおよびB :: Asベクトルが更新されますか?

または、B :: Attach(A)である必要があります。

それらのどちらも正しくないと感じています。コードの操作をやめて、1週間後に戻ってきた場合、A.Attach(B)とB.Attach(A)のどちらを実行するべきかを思い出すことができないと確信しています。

おそらくそれはこのような関数であるべきです:

CreateConnection(A, B);

ただし、グローバル関数を作成することは、クラスAとBのみを操作するための関数であることから、望ましくないように見えます。

別の質問:この問題/要件に頻繁に遭遇した場合、どうにかして一般的な解決策を作成できますか?おそらく、このタイプの関係を共有するクラスから派生したり、クラス内で使用できるTwoWayConnectionクラスですか?

この状況に対処するための良い方法は何ですか... 1対多の「CがDを所有する」状況をうまく処理する方法を私は知っていますが、これはトリッキーです。

編集:より明確にするために、この質問には所有権の問題は含まれていません。AとBの両方が他のオブジェクトZによって所有されており、Zはすべての所有権の問題を処理します。AとBの間の多対多のリンクを作成/削除する方法にのみ興味があります。


私はZ :: Attach(A、B)を作成するので、Zが2種類のオブジェクトを所有している場合は、「正しく感じる」ため、接続を作成できます。私の(あまり良くない)例では、ZはAとBのリポジトリになります。実用的な例がないと、別の方法を見ることができません。
マチャド

1
より正確には、AとBの所有者が異なる場合があります。おそらく、AはZによって所有されているのに対し、BはXによって所有され、XはZによって所有されています。AとBは、リンクされているペアのリストにすばやくアクセスできる必要があります。
Dmitri Shuralyov

私の状況でAとBの本名について知りたければ、それらはPointerGestureRecognizerです。ポインタは、InputManagerクラスによって所有および管理されます。GestureRecognizersはWidgetインスタンスによって所有され、次にWidgetインスタンスがAppインスタンスによって所有されるScreenインスタンスによって所有されます。ポインターはGestureRecognizerに割り当てられ、生の入力データをそれらにフィードできるようになりますが、GestureRecognizerは現在関連付けられているポインターの数を認識する必要があります(1本の指と2本の指のジェスチャーなどを区別するため)。
Dmitri Shuralyov

私はそれについてもっと考えたので、私はちょうど(ポインタベースではなく)フレームベースのジェスチャー認識をしようとしているようです。したがって、ポインタを一度にではなく、一度にイベントをジェスチャに渡すこともできます。うーん。
Dmitri Shuralyov

回答:


2

1つの方法は、パブリックAttach()メソッドと保護されたAttachWithoutReciprocating()メソッドを各クラスに追加することです。彼らのメソッドが他の人を呼び出すことができるようにAB相互の友達を作る:Attach()AttachWithoutReciprocating()

A::Attach(B &b) {
    Bs.push_back(&b);
    b.AttachWithoutReciprocating(*this);
}

A::AttachWithoutReciprocating(B &b) {
    Bs.push_back(&b);
}

に同様のメソッドを実装する場合B、呼び出すクラスを覚えておく必要はありませんAttach()

私はあなたがその振る舞いMutuallyAttachableを両方AからB継承するクラスにまとめることができると確信しています。こうすることで自分自身の繰り返しを避け、ジャッジメントデーにボーナスポイントを獲得することができます。しかし、洗練されていない両方の場所に実装するアプローチでさえ、仕事は完了します。


1
これは、私の頭の中で考えていた解決策(つまり、Attach()をAとBの両方に配置すること)のように聞こえます。また、この動作が必要なときはいつでも派生元として派生できるMutuallyAttachableクラスを使用する、よりドライなソリューション(コードの複製は本当に嫌いです)も気に入っています(かなり頻繁に予想されます)。他の人から提供された同じ解決策を見るだけで、私はそれにもっと自信が持てます、ありがとう!
Dmitri Shuralyov

IMOはこれまでに提供された最良のソリューションであるため、これを受け入れます。ありがとう!ここでそのMutuallyAttachableクラスを実装し、予期しない問題が発生した場合はここで報告します(まだ多くの経験を積んでいない現在の多重継承により、いくつかの問題が発生する可能性もあります)。
Dmitri Shuralyov

すべてがスムーズに機能しました。しかし、元の質問に対する私の最後のコメントによると、私が当初想定していた機能にはこの機能は必要ないことに気付き始めています。これらの2方向の接続を追跡する代わりに、すべてのペアをパラメーターとして、それを必要とする関数に渡します。ただし、これは後で役立つ場合があります。
ドミトリシュラリョフ

MutuallyAttachable誰かが再利用したい場合は、このタスク用に私が書いたクラスを以下に示します。goo.gl / VY9RB(Pastebin)編集:このコードは、テンプレートクラスにすることで改善できます。
ドミトリシュラリョフ

1
MutuallyAttachable<T, U>クラステンプレートをGistに配置しました。 gist.github.com/3308058
Dmitri Shuralyov

5

関係自体が追加のプロパティを持つことは可能ですか?

その場合は、別のクラスにする必要があります。

そうでない場合は、往復のリスト管理メカニズムで十分です。


それが別個のクラスである場合、AはそのBのリンクされたインスタンスをどのようにして知り、BはAのリンクされたインスタンスをどのようにして知るのでしょうか?その時点で、AとBの両方がCと1対多の関係を持つ必要がありますね。
ドミトリシュラリョフ

1
AとBは両方ともCインスタンスのコレクションへの参照を必要とします。Cはリレーショナルモデリングにおける「ブリッジテーブル」と同じ目的を果たします
Steven A. Lowe

1

通常、このように厄介な状況に遭遇したが、理由がはっきりと分からない場合は、間違ったクラスに何かを入れようとしているためです。クラスのうちのいくつかの機能を移動する方法ほとんど常にありますAし、B物事をより明確にするためには。

関連付けをに移動する候補の1つは、最初に関連付けを作成するコードです。多分attach()それを呼ぶ代わりに単にのリストを保存しますstd::pair<A, B>。関連を作成する場所が複数ある場合は、1つのクラスに抽象化できます。

移動の別の候補は、Zあなたの場合、関連するオブジェクトを所有するオブジェクトです。

関連付けを移動するもう1つの一般的な候補は、またはオブジェクトのメソッドを頻繁に呼び出すコードです。たとえば、関連付けられたすべてのオブジェクトを内部で処理するを呼び出す代わりに、関連付けを認識し、それぞれの関連付けを呼び出します。繰り返しになりますが、複数の場所がそれを呼び出す場合は、1つのクラスに抽象化できます。ABa->foo()Ba->foo(b)

多くの場合、関連付けを作成、所有、および使用するオブジェクトは、同じオブジェクトです。これにより、リファクタリングが非常に簡単になります。

最後の方法は、他の関係から関係を導き出すことです。たとえば、兄弟姉妹は多対多の関係ですが、兄弟クラスの姉妹のリストを保持したり、その逆を行うのではなく、親関係からその関係を派生させます。この方法のもう1つの利点は、単一の真の情報源が作成されることです。リストが1つしかないため、バグまたはランタイムエラーによって兄弟、姉妹、および親のリストの間に不一致が生じる可能性はありません。

この種のリファクタリングは、常に機能する魔法の式ではありません。個々の状況に適したものを見つけるまで、まだ実験が必要ですが、私はそれが約95%の時間で機能することを発見しました。残りの時間は、ぎこちなく生きていかなければなりません。


0

これを実装している場合は、AとBの両方にAttachを配置します。Attachメソッドの内部では、渡されたオブジェクトに対してAttachを呼び出します。そのようにして、A.Attach(B)またはB.Attach( A)。私の構文は正しくないかもしれません、私は何年もc ++を使用していません:

class A {
  private: std::vector<B *> Bs;

  void Attach (B* obj) {
    // Only add obj if it's not in the vector
    if (std::find(Bs.begin(), Bs.end(), obj) == Bs.end()) {
      //Add obj to the vector
      Bs.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

class B {
  private: std::vector<A *> As;

  void Attach (A* obj) {
    // Only add obj if it's not in the vector
    if (std::find(As.begin(), As.end(), obj) == As.end()) {
      //Add obj to the vector
      As.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

別の方法は、ABペアの静的リストを管理する3番目のクラスCを作成することです。


ABペアのリストを管理するために3番目のクラスCを使用するという別の提案に関する質問:AとBはペアのメンバーをどのようにして知るのですか?AとBのそれぞれに(単一の)Cへのリンクが必要で、Cに適切なペアを見つけるように依頼しますか?B :: Foo(){std :: vector <A *> As = C.GetAs(this); / * Asで何かをする... * /}それとも何か他のことを思い描いていましたか?これはひどく複雑なようです。
Dmitri Shuralyov
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.