7

我正在处理 C#.NET 中的业务问题。我有两个类,名为 C 和 W,它们将在不同时间独立实例化。

C 类的对象需要包含对 W 类的 0 ... n 个对象的引用,即一个 C 对象最多可以包含 n W 个对象。

每个 W 对象都需要包含对 C 类的恰好 1 个对象的引用,即一个 W 对象包含在一个 C 对象中。

C 类的对象通常首先被实例化。稍后,它的 W 内容被发现并实例化。稍后,我需要相互交叉引用 C 和 W 对象。

什么是好的设计模式?实际上,我有涉及三个或四个类的案例,但我们可以讨论两个类以保持简单。

我在想一些简单的事情,比如:

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>设为私有。通过枚举器公开 Ws 列表,而不是直接公开列表。

例如 public List GetWs() { return 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 并将其添加到新容器中。

于 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

嗯,看起来你几乎明白了,有一个小故障——你必须能够在 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

扩展乔恩斯的答案....

如果 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

一种选择是实现System.ComponentModel 下的IContainerIComponent接口。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 回答