Optimizing Factory Methods with Static Delegate Arrays

In my last post, I explained the benefits of using factory classes to achieve polymorphism in your business applications, and demonstrated how implementing such an architecture greatly improves the design and maintainability of your code. In this post (part 2 if you will) I’ll first quickly review the benefits of the factory pattern, and then demonstrate how to refactor the typical switch/case factory implementation to achieve ultra-high performance using a static array of delegates.

Here is the TenderType enum and TenderFactory code from the last post:

public enum TenderType
{
  Cash,
  DebitCard,
  CreditCard,
   //
   // more tender types
   //
}

public static class TenderFactory
{
  public static ITender CreateTender(TenderType tenderType)
  {
    switch (tenderType)
    {
      case TenderType.Cash:
        return new CashTender();

      case TenderType.DebitCard:
        return new DebitCardTender();

      case TenderType.CreditCard:
        return new CreditCardTender();
       //
       // more case statements here
       //
      default:
        throw new Exception("Unsupported tender: " + (int)tenderType);
    }
  }
}

In this example, we have a factory method that accepts a TenderType enum and uses it to create a concrete tender object corresponding to that enum. All the different tender objects implement the ITender interface, so that’s the type returned by the factory. Because the different tender behaviors are encapsulated in the set of concrete tender classes, client code can simply call this factory method to retrieve an ITender object for any tender type and work with that object without knowing the actual type. That is, you can write polymorphic code that doesn’t need to be maintained as concrete type behaviors change or new concrete types are added in the future.

It’s easy to recognize when you should be using this pattern in your own applications. When you find yourself coding the same switch/case ladder repeatedly, that’s a sure indication that your architecture can be improved significantly by using factories and polymorphism. It’s important to sensitize yourself so that you detect this condition early in your development efforts and apply the factory pattern then. It will be a much greater burden to refactor your code to use the factory pattern later on, once you have allowed a proliferation of switch/case statements to spread throughout your code.

So now we’ve got a single switch/case ladder in a centralized factory method, which eliminates the multitudes of duplicate switch/case ladders that would have otherwise been scattered throughout our application’s code. A huge improvement, to be certain, but can we improve it even more? You bet!

The Need For Speed

Because object instantiation is a very common occurrence, factory methods in general need to perform well. A performance bottleneck in the factory will quickly translate to a serious application-wide problem. If you’re dealing with a small set of concrete types (like our tenders example), and/or your factory code is running on a desktop or other single-user environment, the switch/case implementation of the factory method shown above is probably perfectly suitable and won’t require optimization.

In the real world, however, a factory method frequently supports dozens or even hundreds of different concrete types, which means coding a very long switch/case ladder in that method. Next consider the fact that the switch/case ladder is evaluated sequentially at runtime, top-to-bottom. This means that a matching case statement further down the ladder takes longer to reach than one further up the ladder. Again, for a handful of types in a single-user environment, that differential is imperceptible. But if your factory code supports many types through a long switch/case ladder, and runs on a multi-user application server that is servicing many concurrent client requests for new object instances in high demand, then it becomes critical that your factory code executes as quickly as possible.

A Golden Rule of Optimization: Let The Compiler Do The Work

The following alternative implementation is vastly superior to the switch/case version:

public static class TenderFactory
{
  private delegate ITender CreateTenderMethod();

  // Important: The order of delegates in this static array must match the
  //  order that the TenderType enums are defined.
  private static CreateTenderMethod[] CreateTenderMethods = new CreateTenderMethod[]
  {
    delegate() { return new CashTender(); },
    delegate() { return new DebitCardTender(); },
    delegate() { return new CreditCardTender(); },
     //
     // more delegates here
     //
  };

  public static ITender CreateTender(TenderType tenderType)
  {
    var tender = CreateTenderMethods[(int)tenderType].Invoke();
    return tender;
  }
}

This factory code takes a novel approach. Let’s dissect it.

At the top of the class we define a delegate named CreateTenderMethod which takes no parameters and returns an ITender object. We then declare a static array of CreateTenderMethod delegates, and populate it with anonymous methods that return for each concrete tender type. Stop and think about what this means. The static array is allocated in memory and populated with method pointers for each tender type by the compiler at compile-time. So when this assembly loads into memory, the static array just rolls right into the address space with all the elements already mapped to the methods returning the concrete types. There is no runtime hit whatsoever for dynamically allocating storage space for the array from heap and populating it with delegate instances. The work was already done by the compiler. Having the compiler do work at compile time to avoid having to do the work at runtime is one of the golden rules of optimization.

The CreateTender method itself is now ridiculously simple. It takes the incoming enum and converts it to an integer which it uses as an index into the static array. That instantaneously retrieves the correct delegate which points to the method that will return the concrete tender type specified by the enum. In an array of 250 elements, it won’t take any longer to retrieve the delegate for the 250th element than it will for the first. The Invoke method on the delegate actually runs the method and returns the correct ITender derivative to the requesting client. The only important thing to remember when using this technique, which you may have already realized on your own, is that the order of delegates in the array must match the order that the enums are defined (as mentioned in the code comments). A mismatch will obviously manifest itself as a serious runtime error.

What’s really nice here is that anonymous methods added in C# 2.0 greatly reduce the amount of code this solution requires, compared to what was required before anonymous methods. Back then, you’d need to explicitly create one-line methods for each concrete tender type, and then reference each of those one-line methods from an explicit delegate constructor in the static array. So this implementation is now not only significantly faster than the typical switch/case approach, it’s also just as easy to implement and maintain. Don’t you just love it when there are no downsides?

Leveraging Polymorphism with Factory Classes

In this post, I’ll explain the benefits of using the factory pattern, and show you how to code a typical factory implementation to achieve polymorphism in your .NET applications. In my next post, I’ll show you how to optimize the factory method for ultra-high performance by using a static array of delegates.

Working with Different Types

Let’s use a typical POS (Point Of Sale) scenario to illustrate the factory pattern. POS applications run on checkout counters in retail stores, where the customer can pay with a variety of tender types, such as cash, credit, debit, voucher, etc. Each of these types exhibit different behaviors; for example, paying with cash pops open the cash drawer, paying by debit card requires a PIN, paying by credit card requires a signature, etc.

Without using a factory pattern to manage the different tender types, code to determine if a customer signature is required on checkout for a given tender type might look like this:

bool isSignatureRequired;
switch (tenderType)
{
  case TenderType.Cash:
    isSignatureRequired = false;
    break;


  case TenderType.DebitCard:
    isSignatureRequired = false;
    break;


  case TenderType.CreditCard:
    isSignatureRequired = true;   // credit cards require signature
    break;

   //
   // more case statements here
   //

  default:
    throw new Exception("Unsupported tender: " + (int)tenderType);
}

Similarly, to determine if a PIN is required for any given tender type:

bool isPinRequired;
switch (tenderType)
{
  case TenderType.Cash:
    isPinRequired = false;
    break;


  case TenderType.DebitCard:
    isPinRequired = true;   // debit cards require PIN
    break;


  case TenderType.CreditCard:
    isPinRequired = false;
    break;

   //
   // more case statements here
   //

  default:
    throw new Exception("Unsupported tender: " + (int)tenderType);
}

With this approach, the same switch/case “ladder” will appear numerous times scattered throughout your application, each one dealing with a different aspect of each possible tender type. In short order, maintenance will become a nightmare. Changing the behavior of any type means hunting down all the pertinent switch/case blocks and modifying them. Creating a new type means adding a new case to many existing switch/case blocks—wherever they are (good luck finding them all). It’s nearly impossible to gain a clear picture of how the tender types compare to one another, because bits of information about each tender are fragmented in switch/case blocks across the application’s codebase.

Creating a Factory

The factory pattern solves this problem by eliminating all these switch/case blocks from your application, and consolidating the logic for each type in its own class instead. All the classes for each type implement the same interface, so that you can work with an instance of any type without knowing or caring what the concrete type is, and get the information you need.

With a factory pattern in place, you won’t have to search for or modify one line of code in your core application logic when the behavior of an existing tender changes or a tender type is supported in the future. This represents a huge improvement over the scattered switch/case approach.

Here are the steps to implement the pattern:

1) Create the ITender interface:

With this interface, we have defined certain things that we know about every tender type, such as IsSignatureRequired, IsPinRequired, AllowCashBack, etc.

public interface ITender
{
  bool IsSignatureRequired { get; }
  bool IsPinRequired { get; }
  bool AllowCashBack { get; }
  bool PopOpenCashDrawer { get; }
   //
   // more members
   //
}

2) Define enums for each ITender derivative: 

public enum TenderType
{
  Cash,
  DebitCard,
  CreditCard,
   //
   // more tender types
   //
}

3) Create concrete ITender classes. Here are three concrete classes that implement ITender:

public class CashTender : ITender
{
  bool ITender.IsSignatureRequired { get { return false; } }
  bool ITender.IsPinRequired       { get { return false; } }
  bool ITender.AllowCashBack       { get { return false; } }
  bool ITender.PopOpenCashDrawer   { get { return true; } }
}

public class DebitCardTender : ITender
{
  bool ITender.IsSignatureRequired { get { return false; } }
  bool ITender.IsPinRequired       { get { return true; } }
  bool ITender.AllowCashBack       { get { return true; } }
  bool ITender.PopOpenCashDrawer   { get { return false; } }
}

public class CreditCardTender : ITender
{
  bool ITender.IsSignatureRequired { get { return true; } }
  bool ITender.IsPinRequired       { get { return false; } }
  bool ITender.AllowCashBack       { get { return false; } }
  bool ITender.PopOpenCashDrawer   { get { return false; } }
}

4) Create the factory class:

public static class TenderFactory
{
  public static ITender CreateTender(TenderType tenderType)
  {
    switch (tenderType)
    {
      case TenderType.Cash:
        return new CashTender();


      case TenderType.DebitCard:
        return new DebitCardTender();


      case TenderType.CreditCard:
        return new CreditCardTender();


       //
       // more case statements here
       //

      default:
        throw new Exception("Unsupported tender: " + (int)tenderType);
    }
  }
}

With these elements in place, you can handle any ITender derivative throughout your application easily and elegantly. Given an enum for any particular tender type, a single call to the CreateTender factory method will return a new instance of the correct concrete ITender object. You can then work with the properties and methods of the returned instance and get the results expected for the specific tender type, without needing to know the specific tender type or testing for different tender types. This is the essence of polymorphism.

For example, to determine if a signature is required, it’s as simple as:

ITender tender = TenderFactory.CreateTender(tenderType);
bool isSignatureRequired = tender.IsSignatureRequired;

Unlike the code at the top of the post that retrieved this information without using the factory pattern, this code will never change, even as new tenders are added in the future. Adding support for a new tender (for example, food stamps) now involves only adding a new enum, creating a new concrete class that implements all the ITender members for the new tender type, and adding a single case statement to the factory method’s switch/case block. Doing so allows you to instantly plug in new tender types without touching one line of code throughout the body of your core application logic. The code above requires no modifications to determine if a signature is required for a newly added tender.

Enhancing and Optimizing the Factory Method

So now your application calls a method in the factory class to “new up” an ITender instance rather than explicitly instantiating a new ITender object through one of its constructors. You can enhance this pattern by encapsulating all the concrete classes in a separate assembly and scoping their constructors as internal (Friend, in VB) so that clients cannot circumvent the factory method and are instead forced to create new ITender instances by calling the factory method.

Another logical next step would be to create an abstract base class named TenderBase which would house common functionality that all tender types require. TenderBase would implement ITender, and all the concrete tender classes would inherit TenderBase instead of directly implementing ITender (though they will still implement ITender implicitly of course, since TenderBase implements ITender).

It’s important to ensure that your factory methods execute as quickly as possible, especially in scenarios where there is a high-volume demand to create new objects. In my next post, I’ll show you an improved version of the factory method that significantly out-performs the switch/case approach shown here (especially when dealing very a great many entity types), by replacing the switch/case logic with a static array of delegates.

Follow

Get every new post delivered to your Inbox.

Join 36 other followers