我正在解决 C#.NET 中的业务问题。我有两个类,名为 C 和 W,它们将在不同时间独立实例化。
C 类的对象需要包含对 W 类的 0 ... n 个对象的引用,即 C 对象最多可以包含 n 个 W 对象。
每个 W 对象需要包含对 1 个 C 类对象的引用,即一个 W 对象包含在一个 C 对象中。
C 类的对象通常首先被实例化。稍后,它的 W 内容被发现并实例化。稍后,我需要相互交叉引用 C 和 W 对象。
什么是好的设计模式?实际上,我有涉及三到四个类的情况,但我们可以讨论两个类以保持简单。
我在想一些简单的事情,比如:
class C
{
public List<W> contentsW;
}
class W
{
public C containerC;
}
这暂时可行,但我可以预见必须编写大量代码来跟踪所有引用及其有效性。我想实现代码来仅对容器进行浅层刷新并对所有引用的类进行深度刷新。还有其他方法吗?它们的优点是什么?
11/3 编辑: 感谢大家的良好回答和良好讨论。我最终选择了乔普的答案,因为这最接近我想做的事情,但其他答案也有帮助。再次感谢!
如果您有 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 列表,而不是直接公开列表。
例如公共列表 GetWs() { 返回 this.ContentW.ToList(); }
上面的代码正确处理了所有权的转移。假设您有两个 C 实例——C1 和 C2——以及 W 实例——W1 和 W2。
W1.Container = C1;
W2.Container = C2;
在上面的代码中,C1包含W1,C2包含W2。如果将 W2 重新分配给 C1
W2.Container = C1;
那么 C2 将有零个项目,C1 将有两个项目 - W1 和 W2。你可以拥有一个浮动的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 中控制对列表的添加。
例如,
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 容器。
扩展乔恩的答案。
如果 W 不应该让 C 保持存活,则可能需要弱引用。
此外,如果你想转移所有权,添加应该更复杂。
public void AddToContents(W content);
{
if(content.Container!=null) content.Container.RemoveFromContents(content);
content.Container = this;
_contentsW.Add(content);
}
一种选择是实现 System.ComponentModel 下的 IContainer 和 IComponent 接口。 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>
等。幕后更新是通过内部字段完成的,而使用此域模型的代码的更新是通过公共财产。