Share via



April 2013

Volume 28 Number 04

Patterns in Practice - Adding Functionality to an Object: Loading the Right Classes

By Peter Vogel | April 2013

In my January 2013 column I outlined the business problem that I wanted to address: a set of sales options that users apply to order lines as part of purchasing products. In that column I decided that, at a high level, what I was doing was adding functionality to the base class on an as-needed basis. When that functionality is needed is affected by two things: what sales options have been selected and at what point in the sales process the object is being used (order taking, shipping, billing, and so on). I decided that the roles pattern provided the best framework for developing a maintainable, extendable solution.

In practical terms, what using the roles pattern means is that I have lots of dedicated sales option classes—potentially one class for each sales option/processing point (for example, a “giftwrapping for shipping” SalesOption class, a “giftwrapping for billing” class, an “expediting for shipping” class, and so on). The benefit to this design is that I can, ideally, support new sales options (or new functionality for a sales option at some point in the process) without having to touch any other sales option’s code: I add new functionality by writing new classes. The cost is that I end up with lots of sales options classes. And that’s a real cost. As you’ll see, I may not be able to say—just by looking at my code—which class is being loaded at any time. (I’ll come back to this issue at the end of the column, after you’ve seen the code that loads SalesOption objects.)

In my last column, I claimed that this design wouldn’t add any extra code—that this design just distributes the code for processing sales options across more classes. And that’s true for the business-related code. However, I’m going to need some infrastructure code to manage those many SalesOption classes that this design requires and that’s what this column is all about.

Designing the Base SalesOption Class

For any application to work with a variety of SalesOptions, all the SalesOption classes need to support a common interface. This will, for example, allow the OrderLine to have a SalesOptions collection containing a variety of SalesOption objects but still allow an application to write a foreach loop to process all the SalesOptions associated with an OrderLine:

foreach (SalesOptionBase sob in OrderLine.SalesOptions)

Initially, I was going to have all the SalesOption classes implement a common interface. However, I’ve realized that some common code is shared among all SalesOptions, and given that, my SalesOptions will share an interface by inheriting from a common SalesOptionBase class.

That common code will, for example, support notifying applications about changes in a SalesOption’s status. After an application retrieves a SalesOption object from an OrderLine, that SalesOption may become invalidated: the user might, for example, remove the SalesOption from the OrderLine. When that happens, the application needs to be notified that the SalesOption is no longer relevant.

The SalesOption class provides two ways for an application program to be notified that a SalesOption that it retrieved is no longer valid:

  • An IsValid property that returns true or false
  • An event to notify the application that the property has changed

I was originally going to create a custom event that an application could wire up to in order to be notified that a SalesOption is invalid. But in the end I decided to piggyback on the .NET Framework’s INotifyPropertyChanged interface and have my SalesOption classes raise the PropertyChanged event every time the IsValid property changes. That’s part of the common code that goes into my SalesOptionsBase class.

As long as I was creating that class, it made sense to have SalesOptionBase report on its type through a property whose value it set in the class’s constructor. It also made sense to support a SalesOptionDescription property but let the code that creates the SalesOption set that text (I have some default text stored in the SalesOptions table, but applications may want to customize this value—to support internationalization, for example).

Putting all this together, the SalesOptionBase class looks like the code in Figure 1.

Figure 1. The SalesOptionBase class

public class SalesOptionBase: INotifyPropertyChanged
{
  private bool roleStatus;
  public event PropertyChangedEventHandler PropertyChanged;
  public SalesOptionBase(SalesOptionRoles SalesOption)
  {
    this.SalesOptionType = SalesOption;
    this.IsValid = true;
  }
  public SalesOptionType SalesOption{ get; private set;}
  string SalesOptionDescription {get; set;}
  public bool IsValid
  {
    get
    {
      return roleStatus;
    }
    internal set
    {
      roleStatus = value;
      if (PropertyChanged != null)
      {
        PropertyChanged(this,
          new PropertyChangedEventArgs("IsValid"));
      }
    }
  }       
}

The benefit of this work is how little code is required to support a new sales option. To support the expediting sales option during the order-taking part of the process, for example, I create a class that inherits from my base class and set the role type in the class’s constructor to an enumerated value. The initial code for that class would look like this:

 

public class ExpediteShippingSalesOption: SalesOptionBase
{
  public ExpediteShippingSalesOption():
    base(SalesOptionRoles.Expedite)
  {
  }
}

Figure 2 shows SalesOptionBase with three derived classes.

SalesOptionBase provides the functionality shared by all SalesOption classes
Figure 2. SalesOptionBase provides the functionality shared by all SalesOption classes

Defining Access to an OrderLine’s SalesOptions

Now I can start writing the parts of the OrderLine that support working with SalesOptions. First, my OrderLine constructor accepts a collection of SalesOptionData objects (representing data retrieved from the database) and an enumerated value specifying where in the processing cycle the OrderLine is being used. The constructor for the OrderLine just moves those two values to internal variables:

public class OrderLine
{
  private List<SalesOptionData> OptionsForLine = 
    new List<SalesOptionData>();
  private ProcessingCycle CyclePoint;
  public OrderLine(List<SalesOptionData> OptionsForLine,
                 ProcessingCycle CyclePoint)
  {
    this.OptionsForLine = OptionsForLine;
    this.CyclePoint = CyclePoint;

The enumerated value specifying the point in the processing cycle looks like this:

public enum ProcessingCycle {
  OrderTaking,
  Shipping,
  Billing,
  ...more...
}

As I already noted, I need another enumerated value to provide a list of valid SalesOptionTypes:

public enum SalesOptionType
{
  Expedite,
  GiftWrap,
  Discount,
  ...more...
}

And, as I also said earlier, the OrderLine needs (at the very least) a SalesOptions property that exposes all the SalesOptions for the OrderLine as a collection, which I’ll also load in the OrderLine’s constructor. I don’t want that code to be complicated or to change frequently. Unfortunately, my design, with its multiple SalesOption classes, could force me to have complex and volatile code—code that manages multiple kinds of objects and requires an update every time a new sales option is added. Fortunately, there’s a better solution: a dependency injection (Inverson of Control) container.

Loading the SalesOptions Collection

Microsoft provides two dependency injection containers: the Managed Extensibility Framework (MEF) and Unity. For this application I need the ability to select SalesOptions based on the SalesOptionType and the point in the processing cycle where the OrderLine is being used. Out of the box, Unity lets me select on only two options (the class’s interface and a string value), whereas MEF lets me select on as many options as I need (and use enumerated values for my criteria). Furthermore, there may not be a SalesOption object for every point in the processing cycle (if giftwrapping is free, for example, there would no giftwrapping SalesOption for the billing process). Unity raises an exception if no matching class is available, but MEF does not. I’d prefer not to raise exceptions because they hurt performance. Finally, I would like to be able to add SalesOptions just by dropping a compiled DLL with new SalesOption classes into the folder with the application. Out of the box, Unity doesn’t provide that feature, but MEF does. If I was willing to work with some of Unity’s extensions (like the ones that Prism provides for Unity in WPF, for example), I might have chosen Unity. But for this case study I’m using MEF.

The first step in using MEF is to decorate my SalesOption class with attributes that support the criteria I’ll use to retrieve classes. In this case, I use MEF’s Export attribute to specify the class I’ll be looking for (that’s the SalesOptionBase class). I also use MEF’s ExportMetadata attribute to specify values for the sales option and point in the processing cycle that the class supports. (I can use my enumerated values to set these values.) For a class that supports expediting in the shipping process, the attributes would look like this:

[Export(typeof(SalesOptionBase))]
[ExportMetadata("SalesOption", SalesOptionRoles.Expedite)]
[ExportMetadata("CyclePoint", ProcessingCycle.Shipping)]
public class ExpediteShippingSalesOption: SalesOptionBase
{

To issue queries against MEF, I need to define an interface that specifies the criteria that I can use to select classes. To support querying on processing cycle and sales option type, I define this interface:

public interface ISalesOptionMetaData {
  SalesOptionRoles  SalesOption {get; }
  ProcessingCycle CyclePoint {get;}
}

My OrderLine needs a property for MEF to load class definitions into and to provide a focus for queries. That property is defined using the Lazy class to prevent the classes from instantiating before I need them. In the definition of the Lazy class, I specify the shared interface for my SalesOptions and the query interface. I must also decorate the property with MEF’s ImportMany attribute so that the property can hold multiple SalesOption classes. I’ve called that property AllSalesOptions, and it looks like this (because I’ll only use the property internally, I declare it as private):

[ImportMany()]
private IEnumerable<Lazy<SalesOptionBase,
  ISalesOptionsMetaData>> AllSalesOptions { get; set;}

While the property that MEF uses is declared as private, the OrderLine will expose a public property to hold the collection of instantiated SalesOption classes: that will be a simple List. However, I want the List to be a read-only collection so that applications using the OrderLine object have to use methods that I provide to add and remove SalesOptions from the collection. I do that by creating a private collection for my use and a public, read-only collection for applications to use the .NET Framework’s ReadOnlyCollection object:

private List<SalesOptionBase> SalesOptionsInternal { get; set; }
public ReadOnlyCollection<SalesOptionsBase> SalesOptions;

I initialize my internal collection in my OrderLine’s constructor like this:    

this.SalesOptionsInternal = new List<SalesOptionBase>();

Loading the public SalesOptions property is a two-step process. First, I use MEF to load the private AllSalesOptions property with every SalesOption class definition. I want MEF to search for those SalesOptions classes in the folder with my application, so, still in the OrderLine’s constructor, I create an MEF catalog that points to my application’s folder:

string binPath = this.GetType().Assembly.CodeBase;
DirectoryCatalog cat = new DirectoryCatalog(binPath);

I then use the catalog to create an MEF composition container:

CompositionContainer con = new CompositionContainer(cat);

Finally, I call the container’s ComposeParts method, passing a reference to my OrderLine class to cause MEF to find and load my AllSalesOptions property with SalesOption class definitions drawn from the DLLs in my application’s folder:

con.ComposeParts(this);

The AllSalesOptions property will now have all of the SalesOption class definitions.

I’m ready for the second step: selecting the appropriate SalesOption objects (based on the SalesOptions assigned to the OrderLine and the processing cycle) and adding them to my internal SalesOptions list. The instantiated versions of each SalesOption class is found in the Value property of the class definitions in AllSalesOptions.

I can do all this with a single LINQ statement that returns the instantiated classes. In my LINQ statement, I look for classes with their CyclePoint set to the appropriate processing cycle. I also look for classes whose SalesOption matches entries in the OptionsForLine collection that lists the SalesOptions for the OrderLine. My LINQ statement to set my internal collection looks like this:

this.SalesOptionsInternal =
  (from so in this.SalesOptionsBase
  from sod in OptionsForLine
  where so.Value.SalesOption == sod.SalesOptionID
  where so.Metadata.CyclePoint == this.CyclePoint
  select so.Value).ToList();

After I’ve loaded my internal collection, I use the .NET Framework’s AsReadOnly method to initialize my external, read-only property:

this.SalesOptions = this.SalesOptionsInternal.AsReadOnly();

That’s about a dozen lines of code in my OrderLine’s constructor to manage loading the right classes at the right time. Figure 3 shows the OrderLine with the properties that support using MEF to load the SalesOption classes and objects.

The OrderLine class (with properties that support MEF) and the two enumerations it requires
Figure 3. The OrderLine class (with properties that support MEF) and the two enumerations it requires

Design Trade-offs

There’s obviously some “indeterminacy” here. If there’s a problem in production that’s related to the “SalesOption that’s loaded right now,” or if users ask me to modify the “SalesOption that’s loaded right now,” I’m probably not going to be absolutely sure which of my many SalesOption classes is currently being used by the application.

Quite frankly, in those scenarios I’ll probably determine which SalesOption is loaded right now by stepping through the code rather than by deduction. I’ll set up a test with the “right now” conditions, set a breakpoint on a call to a method in the sales option, run the test in debug mode, step into the method and see what class I’m in. If this makes you uncomfortable, then you’ll want to pick a different design with fewer, more complex objects and write the code to load those objects yourself. I wouldn’t guarantee that you’ll end up with more maintainable (or even “less”) code.

Of course, you’ve probably noticed that none of my SalesOptions objects do, well…anything (at least, not yet). And because my external property is read-only, I also need to provide some methods to allow applications to add and remove SalesOptions from the collection. And—if I was a decent human being—I’d provide methods that allow an application to retrieve a particular SalesOption or check to see whether a particular SalesOption is assigned to the OrderLine. That’s all in my next column.

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.