views:

758

answers:

6

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.

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.

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.

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.

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!

+2  A: 

Hmmm, looks like you almost got it, with one minor glitch -- you gotta be able to control the addition to the list within 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.

Jon Limjap
which may or may not work depending on the lifetimes of C and W. if C is supposed to die before W, then W really needs a weak reference to C
Keith Nicholas
I guess that's why I asked about how W should be handled. The requirement seems simple enough to have to worry about W's existence. Maybe a more concrete concept will help clarify the problem.
Jon Limjap
A: 

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.

Charlie
+2  A: 

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.

Rob
+1  A: 

Expanding on Jons Answer....

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

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);
}
Keith Nicholas
A: 

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.

Justice
+4  A: 

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

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.

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.

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

In the code above, C1 contains W1 and C2 contains W2. If you reassign W2 to 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

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.

jop