Search code examples
c#.netclassreferenceclass-design

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!


Solution

  • 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.