7

C#.NET でビジネス上の問題に取り組んでいます。C と W という名前の 2 つのクラスがあり、異なるタイミングで個別にインスタンス化されます。

クラス C のオブジェクトは、クラス W の 0 ... n 個のオブジェクトへの参照を含む必要があります。つまり、C オブジェクトは最大 n 個の W オブジェクトを含むことができます。

各 W オブジェクトには、クラス C の 1 つのオブジェクトへの参照が含まれている必要があります。つまり、W オブジェクトは 1 つの C オブジェクトに含まれています。

クラス C のオブジェクトは通常、最初にインスタンス化されます。後で、その W コンテンツが検出され、インスタンス化されます。この後の時点で、C オブジェクトと W オブジェクトを相互参照する必要があります。

これに適した設計パターンは何ですか? 実際には 3 つまたは 4 つのクラスが関係するケースがありますが、簡単にするために 2 つのクラスについて話すことができます。

私は次のような簡単なことを考えていました:

class C
{
   public List<W> contentsW;

}

class W
{
  public C containerC;

}

これは今のところ機能しますが、すべての参照とその有効性を追跡するためにかなりの量のコードを作成する必要があると予測できます。コンテナーのみの浅い更新と、参照されるすべてのクラスの深い更新を行うコードを実装したいと考えています。他のアプローチはありますか?また、その利点は何ですか?

11/3 に編集: 良い回答と良い議論をありがとう。それが私がやりたいことに最も近かったので、私は最終的にjopの答えを選びましたが、他の答えも役に立ちました。再度、感謝します!

4

6 に答える 6

6

Martin Fowlerのリファクタリングの本をお持ちの場合は、「一方向の関連付けを双方向に変更する」リファクタリングに従ってください。

お持ちでない場合は、リファクタリング後のクラスは次のようになります。

class C
{
  // Don't to expose this publicly so that 
  // no one can get behind your back and change 
  // anything
  private List<W> contentsW; 

  public void Add(W theW)
  {
    theW.Container = this;
  }

  public void Remove(W theW)
  {
    theW.Container = null;
  }

  #region Only to be used by W
  internal void RemoveW(W theW)
  {
    // do nothing if C does not contain W
    if (!contentsW.Contains(theW))
       return; // or throw an exception if you consider this illegal
    contentsW.Remove(theW);
  }

  internal void AddW(W theW)
  {
    if (!contentW.Contains(theW))
      contentsW.Add(theW);
  }
  #endregion
}

class W
{
  private C containerC;

  public Container Container
  {
    get { return containerC; }
    set 
    { 
      if (containerC != null)
        containerC.RemoveW(this);
      containerC = value; 
      if (containerC != null)
        containerC.AddW(this);
    }
  }
}

私がList<W>プライベートにしたことに注意してください。リストを直接公開するのではなく、列挙子を介してWのリストを公開します。

例:public List GetWs(){return this.ContentW.ToList(); }

上記のコードは、所有権の譲渡を適切に処理します。Cの2つのインスタンス(C1とC2)とWのインスタンス(W1とW2)があるとします。

W1.Container = C1;
W2.Container = C2;

上記のコードでは、C1にはW1が含まれ、C2にはW2が含まれています。W2をC1に再割り当てした場合

W2.Container = C1;

その場合、C2にはゼロのアイテムがあり、C1にはW1とW2の2つのアイテムがあります。あなたはフローティングWを持つことができます

W2.Container = null;

この場合、W2はC1のリストから削除され、コンテナはありません。CのAddメソッドとRemoveメソッドを使用してWのコンテナーを操作することもできます。そのため、C1.Add(W2)は、元のコンテナーからW2を自動的に削除し、新しいコンテナーに追加します。

于 2008-10-30T03:38:11.193 に答える
3

私は一般的に次のようなことをします:

class C
{
   private List<W> _contents = new List<W>();
   public IEnumerable<W> Contents
   {
      get { return _contents; }
   }

   public void Add(W item)
   {
      item.C = this;
      _contents.Add(item);
   }
}

したがって、Contentsプロパティは読み取り専用であり、集計のメソッドのみを介してアイテムを追加します。

于 2008-10-30T03:19:29.343 に答える
2

うーん、ほとんど理解できたようですが、小さな不具合が 1 つあります。リストへの追加を C 内で制御できなければなりません。

例えば、

class C
{
    private List<W> _contentsW;

    public List<W> Contents 
    {
        get { return _contentsw; }
    }

    public void AddToContents(W content);
    {
        content.Container = this;
        _contentsW.Add(content);
    }
}

確認するには、リストを反復処理するだけでよいと思います。

foreach (var w in _contentsW)
{
    if (w.Container != this)
    {
        w.Container = this;
    }
}

それが必要かどうかはわかりません。

同じ値を持つ W の複数のインスタンスが存在する可能性がありますが、異なる C コンテナーを持つ可能性があることに注意してください。

于 2008-10-30T03:13:25.487 に答える
1

JonsAnswerを拡張する...

WがCを存続させることになっていない場合は、弱参照が必要になる場合があります。

また...所有権を譲渡したい場合は、追加をもっと複雑にする必要があります...

public void AddToContents(W content);
{  
   if(content.Container!=null) content.Container.RemoveFromContents(content);
    content.Container = this;
    _contentsW.Add(content);
}
于 2008-10-30T03:20:51.103 に答える
0

このための1つのオプションは、 System.ComponentModelの下にあるIContainerおよびIComponentインターフェイスを実装することです。Cがコンテナ、Wがコンポーネントになります。ComponentCollectionクラスはWインスタンスのストレージとして機能し、IComponent.SiteはCへのバックリンクを提供します。

于 2008-10-30T03:15:58.413 に答える
0

これが私が使っているパターンです。

public class Parent {
    public string Name { get; set; }
    public IList<Child> Children { get { return ChildrenBidi; } set { ChildrenBidi.Set(value); } }
    private BidiChildList<Child, Parent> ChildrenBidi { get {
        return BidiChildList.Create(this, p => p._Children, c => c._Parent, (c, p) => c._Parent = p);
    } }
    internal IList<Child> _Children = new List<Child>();
}

public class Child {
    public string Name { get; set; }
    public Parent Parent { get { return ParentBidi.Get(); } set { ParentBidi.Set(value); } }
    private BidiParent<Child, Parent> ParentBidi { get {
        return BidiParent.Create(this, p => p._Children, () => _Parent, p => _Parent = p);
    } }
    internal Parent _Parent = null;
}

明らかに、私にはクラスがBidiParent<C, P>ありBidiChildList<C, P>、後者は実装IList<C>などを実装しています。舞台裏の更新は内部フィールドを介して行われ、このドメインモデルを使用するコードからの更新はパブリックプロパティを介して行われます。

于 2008-10-30T03:37:06.640 に答える