他のクラスを参照する必要があるクラスのC#の良いデザインパターンは何ですか?
-
05-07-2019 - |
質問
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の答えを選びましたが、他の答えも助けました。どうもありがとう!
解決
Martin Fowler's Refactoring bookをお持ちの場合は、単に<!> quot; Change Unidirectional Association to Bidirectional <!> quot;に従ってください。リファクタリング。
お持ちでない場合、リファクタリング後のクラスは次のようになります。
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のリストを公開します。
e.g。 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を自動的に削除し、新しいコンテナに追加します。
他のヒント
通常は次のようにします:
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プロパティは読み取り専用であり、集計のメソッドのみでアイテムを追加します。
うーん、もう少し問題があるように見えますが、C内のリストへの追加を制御できなければなりません。
e.g。、
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;
}
}
それが必要かどうかわからない。
同じ値を持つが異なるCコンテナを持つWのインスタンスが複数存在する可能性があることを理解してください。
Jons Answerで拡張...
WがCを存続させることになっていない場合、弱参照が必要になる場合があります。
また...所有権を譲渡する場合、追加はより複雑になります...
public void AddToContents(W content);
{
if(content.Container!=null) content.Container.RemoveFromContents(content);
content.Container = this;
_contentsW.Add(content);
}
このための1つのオプションは、 IContainer および IComponent インターフェースがシステムの下にあります。 ComponentModel。 Cがコンテナ、Wがコンポーネントになります。その後、 ComponentCollection クラスは、ストレージとして機能します。 Wインスタンス、およびIComponent.SiteはCへのバックリンクを提供します。
これは私が使用するパターンです。
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>
などを実装します。舞台裏での更新は内部フィールドを介して行われますが、このドメインモデルを使用するコードからの更新はパブリックプロパティを介して行われます。