Patterns in Practice

Adding Functionality to an Object: Building the Objects

Peter Vogel

In my January 2013 column, I described a typical business problem: a sales order management system that allows users to apply zero or more sales options to individual order lines in a sales order. Each sales option would be applied in the Order Taking application, where sales orders are created, but the options would also affect processing in other stages of the sales order process (for example, the shipping process would be affected by the gift-wrapping option).

From a code design point of view, this kind of problem is addressed by the roles pattern, which supports adding functionality to an object—in this case, the OrderLine—on an as needed basis—in this case, at points in the processing cycle for orders with specific sales options. From a maintenance point of view, the roles pattern allows me to add new sales options without forcing any existing sales option code (or the OrderLine itself) to be rewritten and, ideally, with minimal or no changes to the sales order system’s various applications.

To support this, my OrderLine objects maintain a collection of SalesOption objects—one SalesOption object for each sales option applied to the OrderLine in the Order Taking application. Any application that processes an OrderLine can ask for a specific SalesOption object or iterate through a collection of all the SalesOption objects, processing each one in turn. To keep the SalesOption classes as simple as possible, a SalesOption class handles only the functionality for one sales option at one point in the sales order process.

Supporting Roles in the Parent Object

At this point, it’s worthwhile to follow a sales order through the process. The Order Taking application reads the SalesOptions and ValidSalesOptionsForProduct tables to provide a list of available sales options to the user. As the user adds OrderLine objects to the order, the OrderLine uses the Microsoft Extensibility Framework to create a corresponding list of SalesOptions (AllSalesOptions) that can be used in this part of the sale order process.

When the user selects a sales option from the list of available sales options, the application calls the OrderLine’s AddSalesOption method. That method finds the right SalesOption class from the AllSalesOptions list, instantiates it, and adds it to a list of applied SalesOptions. The method also returns the SalesOption object to the application:

public SalesOptionBase AddSalesOption(SalesOptionType SalesOption)
{
  if (!this.SalesOptionAssigned(SalesOption))
  {
    SalesOptionBase soResult = (from so in this.AllSalesOptions
      where so.Metadata.SalesOption == SalesOption
      select so).FirstOrDefault().Value;
    this.SalesOptionsInternal.Add(soResult);
  }
  return GetSalesOption(SalesOption);
}

At some point, the sales order, its order lines, and the SalesOptions are saved (SalesOptions are saved the SalesOptionForOrderLine table).

Later in the sales order processing cycle, when an application retrieves an existing sales order, the application should retrieve all the information for the order in one trip to the database: the order header, all the order lines, and all the sales options. The OrderLine expects to be passed a collection of SalesOptionData objects, which the OrderLine uses to populate the list of applied SalesOptions.

When the application needs to use a SalesOption, it calls the OrderLine’s GetSalesOption method, which returns a SalesOption from the collection. Alternatively, the application can iterate through the OrderLine’s SalesOptions collection, processing all the SalesOptions applied to the OrderLine.

Developing the GetSalesOption method raises an interesting issue: what if the application asks for a SalesOption that isn’t in the collection of applied SalesOptions? Stopping the application (throwing an exception) because it asks for a missing SalesOption seems extreme; simply returning null would require the application to constantly check for null after requesting a SalesOption.

Instead, I have the GetSalesOption method return a SalesOptionBase class (the class that all SalesOptions inherit from). The base class’s methods, quite frankly, don’t do much of anything: all the real functionality is in the SalesOptions that inherit from it. The result is that when the application asks for a SalesOption that isn’t there, the application gets a class that it can still work with but will do no harm:

public SalesOptionBase GetSalesOption(SalesOptionType SalesOption)
{
  SalesOptionBase soResult = (from so in this.SalesOptionsInternal
      where so.SalesOption == SalesOption
      select so).FirstOrDefault();
  if (soResult == null)
  {
    soResult = new SalesOptionBase(SalesOption);
  }
  return soResult;
}

I’ll also need two other methods on my OrderLine object:

  • RemoveSalesOption: Accepts a SalesOptionType and removes that SalesOption from the collection of applied SalesOptions (or does nothing if the SalesOption isn’t assigned to the OrderLine).
  • SalesOptionAssigned: Accepts a SalesOptionType and returns true if that SalesOption is in the list of applied SalesOptions (returns false otherwise).

Doing Something Useful

But the SalesOption classes need to actually do something. For most activities, the SalesOption is going to need a reference to the OrderLine object it’s associated with. Given a reference to the OrderLine object, the SalesOption can use the OrderLine’s SalesOrder property to access the SalesOrder and use the OrderLine’s Product property to access the Product on the OrderLine. However, passing an OrderLine object will give a SalesOption access only to the public members of those objects—the SalesOption may need access to some private data.

To handle that, I use a Data Transfer Object (DTO) with properties to hold the OrderLine and whatever “additional data” any SalesOption might require. Here’s a simplified version of that DTO, with one example of that additional data:

public class SalesOptionDTO
{
  public OrderLine {get; set;}
  public int Version {get; set;}
}

I could pass this DTO to the SalesOption’s constructor, but because I’m using MEF, it’s easier just to add a property of type SalesOrderDTO to my SalesOptionBase class. Now, in the OrderLine class, before I add a SalesOption to the collection of applied SalesOptions, I create an instance of my DTO, set its properties, and pass the DTO to the SalesOption’s SalesOptionDTO property.

This puts me in danger of creating circular references between my SalesOption objects and my OrderLine objects, so I’ll add the IDisposable interface to my SalesOptionBase class and, in the resulting Dispose method, set the SalesOptionDTO property to null.

I’ll take a good guess right now at what data my DTO should carry. If down the line some SalesOption requires additional information, I’ll just add more properties to the DTO. The existing SalesOptions will ignore those properties, so the only thing I have to do to implement a new version of the DTO is be sure that I recompile all the SalesOption classes before releasing the new DTO.

At this point, I’ve achieved some of my goals: I can add new SalesOption classes to the sales order process without rewriting any existing SalesOption classes. Applications don’t need to be rewritten as new SalesOptions are added because all SalesOptions inherit from SalesOptionBase. This code will always work, for example, regardless of the SalesOption retrieved:

foreach (SalesOptionBase sob in ol.SalesOptions)
{
}

Adding Functionality

But having retrieved the SalesOption, what does the application do with it? Any particular SalesOption can add whatever methods or properties are required to support its functionality (for example, the GiftwrapForBillingSalesOption class might include a GiftwrapType property). However, to access that property, the billing application needs to cast the SalesOption to GiftwrapForBillingSalesOption. I don’t have a problem with that—my goal was to avoid rewriting the existing SalesOptions and OrderLine classes when adding a new SalesOption. Rewriting the applications that actually use a new SalesOptions may be unavoidable.

But the OrderTaking application poses a special problem: it needs to work with any SalesOption, and I’d prefer not to have to rewrite the Order Taking application every time the company adds a new SalesOption class to the mix. So I add two new virtual members to the SalesOptionBase class: a method called Action and a property called Value. Calling the Action method returns a result and updates the Value property with that result (that way, the application can get the result again without having to reexecute the method). Any SalesOption targeted for the Order Taking application can put the code required by the Order Taking application in the Action method. The Order Taking application can bind to the Value property. Finally, the programming team can establish some conventions about what the Order Taking application needs to get back from the Action method.

To support this kind of flexibility, I have the Action method accept a single parameter, called UserData, of type Object. Applications can call the Action method passing whatever class (or anonymous class) the SalesOption needs for that part of the processing cycle.

For the return type of the Action method (which must also be the data type for the Value property), I take a page from ASP.NET MVC and create a base SalesOptionResult class (see Figure 1). That class has a Success property (which the application can check to determine whether all went well), a Message property containing some value (which the Order Taking application will use to update its user interface), and a property called UserData of type object. By default, I return the UserData parameter passed to the Action method in the UserData property of the SalesOptionResult object.

Figure 1. SalesOptionResult Class

public SalesOptionResult Value {get; set;}
public virtual SalesOptionResult Action(object UserData = null)
{
  SalesOptionResult sor = new SalesOptionResult();
  sor.UserData = UserData
  this.Value = sor;
  return sor;
}
public class SalesOptionResult
{
  public bool Success {get; set;}
  public string Message {get; set;}
  public object UserData {get; set;}
}

Where the SalesOptionResult doesn’t provide enough functionality, developers can inherit from SalesOptionResult and create their own specialized versions. They’ll then have to enhance the Order Taking application to use this new version of the class. But it’s possible that the Order Taking application can process all the SalesOptions on an OrderLine like this:

foreach (SalesOptionBase sob in ol.SalesOptions)
{
  OptionMessages.Append(sob.Action().Message);
}

And, if this works for the Order Taking application, it might also work for the other applications that make up the Sales Order process. It’s conceivable that many applications can simply iterate through the OrderLine’s collection of SalesOptions, calling the Action method for each dedicated object and catching the result—and do that without caring what the individual SalesOptions are.

While it wasn’t one of my goals to avoid rewriting the applications that use the SalesOptions, it’s now at least possible that I might not have to. The SalesOptionBase class that supports this flexibility is shown in Figure 2.

Figure 2. SalesOptionBase Class

public class SalesOptionBase: INotifyPropertyChanged, IDisposable
{
  private bool roleStatus;
  public event PropertyChangedEventHandler PropertyChanged;
  public SalesOptionDTO SalesOptionDTO{ get; set;}
  public SalesOptionType SalesOption{ get; private set;}
  public SalesOptionBase(SalesOptionType SalesOption)
  {
    this.SalesOption = SalesOption;
    this.IsValid = true;
  }
  public SalesOptionResult Value {get; set;}
  public virtual SalesOptionResult Action(object UserValues = null)
  {
    SalesOptionResult sor = new SalesOptionResult();
    this.Value = sor;
    return sor;
  }
  public bool IsValid  {
    get
    {
      return roleStatus;
    }
    internal set
    {
      roleStatus = value;
      if (PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs("IsValid"));
      }
    }
  }
  public void Dispose()
  {
    this.SalesOptionDTO = null;
  }
}

While I’ve taken four columns to get to this point, I’ve really been talking about a morning’s worth of work—though I’m only just now ready to start adding actual sales option functionality. By this time my boss or client is probably asking me whether I’ve managed to get anything actually working (shipping is a feature, after all). However, if I could get some more time to spend on this design, I’d like to make a couple of enhancements to improve performance and make my objects easier to work with. And that’s next month’s 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.

 

Rate: