对于需要引用其他类的类,C#中的优秀设计模式是什么?

时间:2021-01-19 14:11:20

I am working on a business problem in C#.NET. I have two classes, named C and W that will be instantiated independently at different times.

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

An object of class C needs to contain references to 0 ... n objects of class W, i.e. a C object can contain up to n W objects.

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

Each W object needs to contain a reference to exactly 1 object of class C, i.e. a W object is contained in one C object.

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

An object of class C is usually instantiated first. At a later point, its W contents are discovered, and instantiated. At this later point, I need to cross reference the C and W objects to each other.

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

What is a good design pattern for this? I actually have cases where I have three or four classes involved but we can talk about two classes to keep it simple.

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

I was thinking of something simple like:

我在考虑一些简单的事情:

class C
{
   public List<W> contentsW;

}

class W
{
  public C containerC;

}

This will work for the moment but I can foresee having to write a fair amount of code to keep track of all the references and their validity. I'd like to implement code down the road to do shallow refreshes of just the container and deep refreshes of all referenced classes. Are there any other approaches and what are their advantages?

这将暂时起作用,但我可以预见必须编写相当数量的代码来跟踪所有引用及其有效性。我想在后面实现代码,只对容器进行浅刷新,并对所有引用类进行深度刷新。还有其他方法,它们有什么优势?

Edit on 11/3: Thanks to all for the good answers and good discussion. I finally chose jop's answer because that came closest to what I wanted to do, but the other answers also helped. Thanks again!

编辑11/3:感谢所有人的好答案和良好的讨论。我最终选择了jop的答案,因为它最接近我想要的,但其他答案也有帮助。再次感谢!

6 个解决方案

#1


6  

If you have the Martin Fowler's Refactoring book, just follow the "Change Unidirectional Association to Bidirectional" refactoring.

如果您有Martin Fowler的重构书,请按照“将单向关联更改为双向”重构。

In case you don't have it, here's how your classes will look like after the refactoring:

如果你没有它,这里是你的类在重构后的样子:

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);
    }
  }
}

Take note that I've made the List<W> private. Expose the list of Ws via an enumerator instead of exposing the list directly.

请注意,我已将List 设为私有。通过枚举器公开Ws列表,而不是直接公开列表。

e.g. public List GetWs() { return this.ContentW.ToList(); }

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

The code above handles transfer of ownership properly. Say you have two instances of C -- C1 and C2 - and the instances of W -- W1 and W2.

上面的代码正确处理所有权的转移。假设您有两个C - C1和C2实例 - 以及W - W1和W2的实例。

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

In the code above, C1 contains W1 and C2 contains W2. If you reassign W2 to C1

在上面的代码中,C1包含W1,C2包含W2。如果你将W2重新分配给C1

W2.Container = C1;

Then C2 will have zero items and C1 will have two items - W1 and W2. You can have a floating W

然后C2将有零项目,C1将有两个项目 - W1和W2。你可以有一个漂浮的W.

W2.Container = null;

In this case, W2 will be removed from C1's list and it will have no container. You can also use the Add and Remove methods from C to manipulate W's containers - so C1.Add(W2) will automatically remove W2 from it's original container and add it to the new one.

在这种情况下,W2将从C1的列表中删除,它将没有容器。您还可以使用C中的Add和Remove方法来操作W的容器 - 因此C1.Add(W2)将自动从其原始容器中删除W2并将其添加到新容器中。

#2


3  

I generally do it something like this:

我通常会这样做:

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);
   }
}

Thus, your Contents property is readonly and you add items through your aggregate's method only.

因此,您的Contents属性是只读的,您只能通过聚合方法添加项目。

#3


2  

Hmmm, looks like you almost got it, with one minor glitch -- you gotta be able to control the addition to the list within C.

嗯,看起来你几乎得到它,有一个小故障 - 你必须能够控制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);
    }
}

For checking, you just have to iterate through your list, I think:

为了检查,你只需要遍历你的列表,我想:

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

Not sure if that's what you need.

不确定这是否是你需要的。

Do realize that there may be multiple instances of W that would have the same values but may have different C containers.

要意识到可能有多个W的实例具有相同的值但可能具有不同的C容器。

#4


1  

Expanding on Jons Answer....

扩展Jons答案....

You may need weak references if W isnt supposed to keep C alive.

如果W不应该保持C存活,则可能需要弱引用。

Also...the add should be more complicated if you want to transfer ownership...

另外......如果要转让所有权,添加应该更复杂......

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

#5


0  

One option for this would be to implement the IContainer and IComponent interfaces found under System.ComponentModel. C would be the container, and W the component. The ComponentCollection class would then serve as the storage for your W instances, and IComponent.Site would provide the back-link to C.

一种选择是实现System.ComponentModel下的IContainer和IComponent接口。 C是容器,W是组件。然后ComponentCollection类将用作W实例的存储,而IComponent.Site将提供C的反向链接。

#6


0  

This is the pattern I use.

这是我使用的模式。

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;
}

Obviously, I have classes BidiParent<C, P> and BidiChildList<C, P>, the latter of which implements IList<C>, etc. Behind-the-scenes updates are done through the internal fields, while updates from code which uses this domain model are done through the public properties.

显然,我有类BidiParent 和BidiChildList ,后者实现IList 等。幕后更新是通过内部字段完成的,而更新来自使用的代码此域模型是通过公共属性完成的。 ,p> ,p>

#1


6  

If you have the Martin Fowler's Refactoring book, just follow the "Change Unidirectional Association to Bidirectional" refactoring.

如果您有Martin Fowler的重构书,请按照“将单向关联更改为双向”重构。

In case you don't have it, here's how your classes will look like after the refactoring:

如果你没有它,这里是你的类在重构后的样子:

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);
    }
  }
}

Take note that I've made the List<W> private. Expose the list of Ws via an enumerator instead of exposing the list directly.

请注意,我已将List 设为私有。通过枚举器公开Ws列表,而不是直接公开列表。

e.g. public List GetWs() { return this.ContentW.ToList(); }

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

The code above handles transfer of ownership properly. Say you have two instances of C -- C1 and C2 - and the instances of W -- W1 and W2.

上面的代码正确处理所有权的转移。假设您有两个C - C1和C2实例 - 以及W - W1和W2的实例。

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

In the code above, C1 contains W1 and C2 contains W2. If you reassign W2 to C1

在上面的代码中,C1包含W1,C2包含W2。如果你将W2重新分配给C1

W2.Container = C1;

Then C2 will have zero items and C1 will have two items - W1 and W2. You can have a floating W

然后C2将有零项目,C1将有两个项目 - W1和W2。你可以有一个漂浮的W.

W2.Container = null;

In this case, W2 will be removed from C1's list and it will have no container. You can also use the Add and Remove methods from C to manipulate W's containers - so C1.Add(W2) will automatically remove W2 from it's original container and add it to the new one.

在这种情况下,W2将从C1的列表中删除,它将没有容器。您还可以使用C中的Add和Remove方法来操作W的容器 - 因此C1.Add(W2)将自动从其原始容器中删除W2并将其添加到新容器中。

#2


3  

I generally do it something like this:

我通常会这样做:

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);
   }
}

Thus, your Contents property is readonly and you add items through your aggregate's method only.

因此,您的Contents属性是只读的,您只能通过聚合方法添加项目。

#3


2  

Hmmm, looks like you almost got it, with one minor glitch -- you gotta be able to control the addition to the list within C.

嗯,看起来你几乎得到它,有一个小故障 - 你必须能够控制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);
    }
}

For checking, you just have to iterate through your list, I think:

为了检查,你只需要遍历你的列表,我想:

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

Not sure if that's what you need.

不确定这是否是你需要的。

Do realize that there may be multiple instances of W that would have the same values but may have different C containers.

要意识到可能有多个W的实例具有相同的值但可能具有不同的C容器。

#4


1  

Expanding on Jons Answer....

扩展Jons答案....

You may need weak references if W isnt supposed to keep C alive.

如果W不应该保持C存活,则可能需要弱引用。

Also...the add should be more complicated if you want to transfer ownership...

另外......如果要转让所有权,添加应该更复杂......

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

#5


0  

One option for this would be to implement the IContainer and IComponent interfaces found under System.ComponentModel. C would be the container, and W the component. The ComponentCollection class would then serve as the storage for your W instances, and IComponent.Site would provide the back-link to C.

一种选择是实现System.ComponentModel下的IContainer和IComponent接口。 C是容器,W是组件。然后ComponentCollection类将用作W实例的存储,而IComponent.Site将提供C的反向链接。

#6


0  

This is the pattern I use.

这是我使用的模式。

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;
}

Obviously, I have classes BidiParent<C, P> and BidiChildList<C, P>, the latter of which implements IList<C>, etc. Behind-the-scenes updates are done through the internal fields, while updates from code which uses this domain model are done through the public properties.

显然,我有类BidiParent 和BidiChildList ,后者实现IList 等。幕后更新是通过内部字段完成的,而更新来自使用的代码此域模型是通过公共属性完成的。 ,p> ,p>