June 2013

Volume 28 Number 6

Patterns in Practice - Adding Functionality to the Object: Refining the Design

By Peter Vogel | June 2013

For the past few months, I’ve been exploring how an extendable, maintainable design for a common business problem can be developed based on the role pattern. (See the sidebar “Read Them All” for the links.) The business problem I used is a common one: order lines in a sales order that are assigned multiple sales options. The role patterns move the functionality required by each sales option into SalesOption classes that are added, removed, or used with OrderLine objects, as required by the various applications that process OrderLine objects. The primary benefit of this design is that additional SalesOption classes can be created without modifying either existing SalesOption or OrderLine classes. Applications that use particular SalesOption objects might need to be modified to take advantage of new SalesOptions, but the design minimizes even those changes. Ideally, the organization could define a new sales option, write a new Sales Option class, compile the class and throw the resulting DLL into the folder where the OrderLine object looks for them.

However, while the design works, it’s not terribly efficient or as easy to work with as I’d like. This may not be an immediate concern. For example, I may be worried about performance only if the number of processed OrderLines/SalesOptions causes a bottleneck in production; I may be worried about ease of use only if there are sufficient developers building applications that use SalesOptions that those developers form a bottleneck in development. But assuming that both of those conditions are true, here are the changes I’d make.

Finding SalesOptions

In my April, 2013 column, I used Microsoft’s Managed Extensibility Framework (MEF) to have the OrderLine load the SalesOption classes. Using MEF isn’t essential to implementing the roles pattern, but it does make it considerably easier to add new SalesOption classes to an application without having to rewrite the OrderLine object.

In the design, the OrderLine object has a property called AllSalesOptions that holds a List of objects that inherit from SalesOptionBase (as all my SalesOption classes do):

[ImportMany]
public IEnumerable<Lazy<SalesOptionBase,
     ISalesOptionMetadata>> AllSalesOptions;

The first step in retrieving the SalesOptions objects is in the OrderLine’s constructor, which loads that property with all of the available SalesOptions using MEF’s ComposeParts method:

this.AllSalesOptions = new List<SalesOptionBase>();
string binPath = this.GetType().Assembly.CodeBase;
DirectoryCatalog cat = new DirectoryCatalog(binPath);
CompositionContainer con = new CompositionContainer(cat);

However, as I discussed in my February column, any particular OrderLine needs only some of the SalesOptions—just the ones that correspond to the sales options listed in the tables that link order lines to sales options. The second step in loading the SalesOption classes, therefore, is the following code, which selects from the earlier collection those SalesOptions listed in a collection passed to the OrderLine. The resulting collection of SalesOptions actually applied to the OrderLine is stored in another property called SalesOptionsInternal. This example retrieves the SalesOptions for a specific point in a sales order’s processing cycle:

this.SalesOptionsInternal =
      (from so in this.AllSalesOptions
       from sod in AppliedSalesOptionTypes
       where so.Value.SalesOption == sod.SalesOptionID &&
             so.Metadata.CyclePoint == this.CyclePoint
       select so.Value).ToList();

There’s nothing wrong with this code except, of course, that it’s executed as every single OrderLine is created. To reduce the overhead, each OrderLine should be responsible for retrieving only the SalesOptions it needs, but not for constantly rebuilding the list of all SalesOptions available.

The right move is to create an object that contains all the available SalesOptions to pass to each OrderLine, so I shift the initial set of code out of the OrderLine’s constructor and into this new class’s constructor, as shown in Figure 1.

Figure 1 The SalesOptionsManager Class

class SalesOptionsManager
{
  private CompositionContainer cc;
  public SalesOptionsManager()
  {
    AssemblyCatalog asmCat =
        new AssemblyCatalog(this.GetType().Assembly);
    DirectoryCatalog dirCat =
        new DirectoryCatalog(
            System.IO.Path.GetDirectoryName(
              this.GetType().Assembly.Location));
    AggregateCatalog aggCat = new AggregateCatalog(asmCat);
    cc = new CompositionContainer(aggCat);
    cc.ComposeParts(this);
  }
  [ImportMany]
  public IEnumerable<Lazy<SalesOptionBase,
      SalesOptionBaseMetadata>> AllSalesOptions;
}

Now, when the application instantiates the Order object, the Order object instantiates the SalesOptionsManager object and passes that SalesOptionsManager to each OrderLine. The constructor for the OrderLine accepts this new object and retrieves from its AllSalesOptions property just the SalesOption objects the OrderLine needs. The new OrderLine constructor, along with the fields the OrderLine class needs, looks like what’s shown in Figure 2

Figure 2 The OrderLine Class

public class OrderLine
{
  private SalesOptionEnvironment Environment;
  private SalesOptionsManager soContainerManager;
  private List<SalesOptionBase> sos;
  public OrderLine(List<SalesOptionTypes> AppliedSalesOptionTypes,
                   SalesOptionsManager som,
                   SalesOptionEnvironment Environment)
  {
    this.Environment = Environment;
    this.sos = new List<SalesOptionBase>();
    this.SalesOptions = this.sos.AsReadOnly();
    foreach (SalesOptionType sot in AppliedSalesOptionTypes)
    {
      if (!this.AddSalesOption(sot))
      {
         //your choice--throw error or add default object
      }
    }

Managing the SalesOption Collection

To ensure that only the code in the OrderLine class can manage the OrderLine’s SalesOptions, I’ve made the public property (SalesOptions) that exposes SalesOptions objects a read-only collection managed through an internal collection called sos. I also refactored the code that adds SalesOptions to put the code in a method called AddSalesOptions (external clients can also use this method to add valid SalesOptions to the OrderLine—the order-taking application would need this, for example). The AddSalesOption method determines if a valid SalesOption exists in the AppliedSalesOptions collection for the SalesOptionType passed to it and, if the role exists, adds the SalesOption to the OrderLine’s internal collection of SalesOptions, sos, as shown in Figure 3.

Figure 3 The AddSalesOptions Method

public bool AddSalesOption(SalesOptionType requestedSalesOption)
{
  SalesOptionBase so =
          (from r in soContainerManager.AllSalesOptions
           where r.Metadata.SalesOption == requestedSalesOption &&
                 r.Metadata.CyclePoint == this.CyclePoint
           select r.Value).FirstOrDefault();
  if (so == null)
  {
    so = (from r in soContainerManager.AllSalesOptions
             where r.Metadata.SalesOption == requestedSalesOption
             select r.Value).FirstOrDefault();
  }
  if (so != null)
  {
    this.sos.Add(so);
    return true;
  }
  return false;
}

Now that I’ve improved the performance of the SalesOption management process, it’s worth considering how to make SalesOptions easier for an application to use.

Managing Events

The sales management system that these sales options are part of is made up of a number of applications. When one of these applications retrieves a SalesOption object, there are now two sets of code referencing the SalesOption object: The OrderLine class the SalesOption was retrieved from and the application that’s using the SalesOption object. Interactions with the OrderLine could cause the OrderLine to remove the SalesOption from the OrderLine’s collection of SalesOptions. It’s critical, therefore, that the application be notified if a SalesOption is no longer valid.

I supported notifying the application that the SalesOption was invalid using two mechanisms. First, I had the SalesOption implement a property that reports its status. The RemoveSalesOption method sets that property before removing an object from the OrderLine’s collection:

public bool RemoveSalesOption(SalesOptionType sot)
{
  SalesOptionBase so = (from s in sos
                        where s.Type == sot
                        select s).FirstOrDefault();
  if (so != null)
  {
    so.Status = SalesOptionStatus.Invalid
    this.sos.Remove(so);
    return true;
  }
  return false;
}

But if all I did was set the status, I’d force an application using the SalesOption to check the status property every time it used the object. So I also had each SalesOption raise an event notifying the application of a change in the object’s status. Unfortunately, that solution forces the application to wire up an event for each SalesOption it retrieves. So, an even better solution is to provide a container that manages the events for all of an OrderLine’s SalesOptions. As a side effect of this change, the OrderLine class gets much simpler.

I begin by creating a new class called SalesOptionsManager and move all of the SalesOption management code out of the OrderLine (the AddSalesOption and RemoveSalesOption methods, among others) into the new class. I also move the property that holds the SalesOptions applied to the OrderLine to this new class. The OrderLine gets a new property—SalesOptionsCollection—that holds this SalesOptionManager class. The OrderLine’s constructor now just creates a SalesOptionsManager class and puts it into that property:

public class OrderLine
{
  public OrderLine(List<SalesOptionType> SalesOptions,
                   SalesOrderContainerManager socm,
                   SalesOptionEnvironment Environment)
  {
    this.SalesOptionCollection =
       new SalesOptionsManager(SalesOptions, 
       socm, Environment);         
  }
  public SalesOptionsManager SalesOptionsCollection 
    { get; internal set; }
}

An application that loops through the SalesOptions would now look like this:

foreach (SalesOption so in
  ordln.SalesOptionCollection.SalesOptionsInternal)
{
   //…work with SalesOption
}

Building a SalesOptionsManager

Here’s what the SalesOptionsManager class with the definition for the event to be raised when a SalesOption is removed from the collection looks like:

public class SalesOptionsManager
{
  public event EventHandler<SalesOptionStatusChangedEventArgs>
                           SalesOptionStatusChanged;

The SalesOptionStatusChangedEventArgs class is used in the event to pass information to the application about the change in the SalesOption. That class looks like this:

public class SalesOptionStatusChangedEventArgs : System.EventArgs
{
  public SalesOption SalesOptionBase { get; internal set; }
  public SalesOptionStatus OldStatus { get; internal set; }
  public SalesOptionStatus NewStatus { get; internal set; }
}

In the SalesOptionsManager class’s RemoveSalesOption method, I notify anyone listening that the SalesOption is now invalid by raising the event, passing my new event object, as shown in Figure 4.

Figure 4 The RemoveSalesOption Method

public bool RemoveSalesOption(SalesOptionType sot)
{
  SalesOptionBase so = (from s in sos
                        where s.Type == sot
                        select s).FirstOrDefault();
  if (so != null)
  {
    so.Status = SalesOptionStatus.Invalid;
    this.sos.Remove(so);
    if (SalesOptionStatusChanged != null)
    {
      SalesOptionStatusChangedEventArgs ev =
           new SalesOptionStatusChangedEventArgs
           {
              NewStatus = SalesOptionStatus.Invalid,
              OldStatus = SalesOptionStatus.Valid,
              SalesOption = so
           };
      SalesOptionStatusChanged(this, ev);
    }
    return true;
  }
  return false;
}

An application that uses Orderlines and wants to catch the event would use code like this:

ord.SalesOptionsCollection.SalesOptionStatusChanged
                 += SalesOptions_SalesOptionStatusChanged;

An event method that catches the event might look like this:

void SalesOptions_SalesOptionStatusChanged(object sender,
SalesOptionStatusChangedEventArgs e)
{
  if (e.NewStatus == SalesOptionStatus.Invalid)
  {
    //...find corresponding SalesOption and set to nothing
  }
}

Another Perspective on Why Design Patterns Matter

If you’ve been following along through this odyssey, you’ll have noticed that my design “evolved” as it grew. Smarter developers than me might have figured out the answer in advance on paper using pseudocode, and then just typed the real code in. However, I find that I think best when I think “in code.” I type an initial sketch of my solution in Visual Studio and, looking at it, realize it’s not the best design. Effectively, I use my programming language and editor as a pseudocode editor for working through my design.

When I finally do have some working code, though, I’m usually still not satisfied with my design. As I’ve worked on the problem, I’ve learned more about it and regret decisions I made earlier in a state of (relative) ignorance. Successive refactorings of my initial working code lead me to a better design than my initial implementation.

This process could be a recipe for confusing and ugly code—except for design patterns. Having an overall pattern in mind means that I’m working towards some goal rather than just “messing with code.” Also, having a repertoire of good coding practices helps ensure an acceptable result. Test Driven Development, for example, ensures that after I’ve made a change to “enhance” the code, the code still does what it used to do.

I have no doubt that even more refactorings would lead to an even better design than the one I ended up with. However, there are limits to my boss/client’s patience and it’s time to move onto other tasks.

Speaking of which: I’ll be taking a break from this column for the next few months (making room for a special edition of MSDN magazine and working on a book about the Web API for Apress). But when the column returns, I’ll be looking at a more “contained” pattern: managing a user interface with lots of functionality…and lots of rules about what the user is and isn’t allowed to do.

Read Them All

In case you missed any of my previous columns, here’s where to find them:


Peter Vogel is the principal system architect in PH&V Information Services, specializing in SharePoint and service-oriented architecture (SOA) development, with expertise in user interface design. In addition, Peter is the author of four books on programming and wrote Learning Tree International’s courses on SOA design ASP.NET development taught in North America, Europe, Africa and Asia.