Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a good design pattern in C# for classes that need to reference other classes?

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!

like image 528
Kevin P. Avatar asked Oct 30 '08 03:10

Kevin P.


4 Answers

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.

e.g. 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.

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.

like image 191
jop Avatar answered Oct 20 '22 00:10

jop


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.

like image 31
Rob Avatar answered Oct 19 '22 23:10

Rob


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.

like image 30
Jon Limjap Avatar answered Oct 20 '22 00:10

Jon Limjap


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);
}
like image 45
Keith Nicholas Avatar answered Oct 19 '22 23:10

Keith Nicholas