Practical .NET

Controlling Process Flow with the Template Method Pattern

If you have a process that needs to be performed -- but with variations -- then implementing the Template Method pattern will give you simpler, more extensible code. You’ll also recognize this pattern from all the times you work with it in the .NET Framework.

Often, when we’re implementing what’s normally called "application workflow," we do it by adding code to our application’s UI. That’s not a bad thing except that, now, that complexity is tied up in a place where it’s harder to automate testing and impossible to share. A better solution is to move that process code out to a standalone class that the client (the application’s UI) can call as necessary.

Externalizing the code into a "process class" can be especially useful when the process is used in several different places and, especially, if the process requires customization in any of those places. Examples include the process that all employees go through as part of being hired … but that has to be tailored for different kinds of employees; the process for purchasing stuff from vendors . . . but varies depending on whether you’re buying a thing or a service; the process for generating a request for a quotation . . . but must be customized depending on the client and the project.

With this design, when a client needs to perform the process, it retrieves the appropriate entity objects, instantiates the process class and calls the methods on the process objects in the right order, passing the entity objects where required.

Options for Customization
There are a couple of ways to customize this process. First, the process can expose multiple fine-grained methods, each of which does one part of the task. With that design clients can pick and choose which methods to call.

If there’s some typical combination of methods that most clients call most of the time, you might wrap those methods inside another method in the process class. With this design (a variation on the Façade pattern), to perform the typical task the client only calls that single method. A class to handle processing a salesorder (including making sure that the customer can pay for it, sending an e-mail to the customer, and passing the order on to both accounting and order fulfillment) might look like Listing 1.

Listing 1: The Start of a Process Class
Public Class ProcessOrder
  Private cust As Customer
  Private Order As SalesOrder  

  Public Sub New (Cust As Customer, Order As SalesOrder)
    Me.Cust = Cust
    Me.Order = Order
  End Sub

  Public Sub Process
    If CheckCredit() Then
      NotifyCustomer()
      NotifyAccounting()
      NotifyOrderShipping()
    End If
  End Sub

  Public Sub NotifyCustomer()
  '...rest of the methods...

With this design, you have, as a developer, another way to customize the process to further simplify life for your clients: Implement the Template Method pattern. In the Template Method pattern, you create new process classes, based on the original class, customizing the methods that make up the process to support the different variations on the process.

Typically, I find myself re-factoring myself into this pattern. I start out with what I think is a simple process class and, eventually, find it becoming too complicated because it’s handling "too many" variations. At that point, I create a base class and create a process class for each variation, with all of them inheriting from my base class.

While you end up with more process classes, each class is relatively straightforward because it only handles one variation of the process. Furthermore, it isolates the variations from each other: If you have to tweak one of the variations, you modify that variation’s process class -- there's no danger that you’ll introduce a bug into the other variations. If a new variation comes along, you create a new class -- again, there’s no danger that you’ll introduce a bug in any of the existing variations.

For example, my earlier code assumed that all orders must be paid for but some orders are "no payment" orders (if, for example, this order is making up for some earlier mistake). My code also assumes that all orders must be shipped, but orders for digital content only need to be downloaded. It may be that only the NotifyCustomer method is identical for all salesorders.

Customizing with the Template Method Pattern
Rather than force the client to make these decisions or create a single, complicated process class, it might be easier for everybody if you created separate DigitalContentOrder, NoChargeOrder and (of course) DigitalNoChargeOrder classes. The Template Method pattern describes how to create those variations in an extensible way that simplifies life for an application.

What changes in a process class that implements the Template Method pattern is that some methods either become overridable (those methods with some code) or abstract (those methods with no code at all).

The base version of CheckCredit, for example, might be marked as overridable (virtual in C#), but have an implementation that always returns True:

Protected Overrideable Function CheckCredit() As Boolean
  Return True
End Function

The "no-payment" version of this class won’t need to override this method. Any other class will almost certainly need to, each with their own code. A version of this class for PremiumCustomers (who have virtually no credit limit) would have very different code in CheckCredit than a class targeting CashOnlyCustomers, for example.

Most classes will need to post charges to Accounting and, probably, they will all do it the same way. However, that method must still be overridable because, at least, the no-payment version will need to provide its own special version of this method:

Public Overridable Sub NotifyAccounting()
  '...default code to handle most SalesOrders
End Sub

I might be wrong about my assumption that most versions will need this default code, though. If I found that a NotifyAccounting method that supported many purchases was getting too complicated, I might prefer each version of this class to have its own special version of NotifyAccounting. I bet that, even then, that each of those methods would share some common code. In that case, I would put that common code in my base method and any method that overrides the method would include a call to my base method. A typical version of NotifyAccounting in a derived class might look like this:

Public Overrides Sub NotifyAccounting()
  '...unique code...
  MyBase.NotifyAccounting
End Sub

When it comes to NotifyShipping, however, there’s no common code or default implementation: Shipping a thing is nothing like enabling a digital download. That method, therefore, ends up with no implementation at all so I mark the class as MustOverride (abstract in C#). I should also probably give the method a better name:

Public MustOverride Sub NotifyFulfillment()

Marking any method as MustOverride will also force me to add MustInherit to my class definition (abstract in C#).

Other Options
There are further variations on this method. In my base class I might insert methods around the "real" process methods, like the ones in boldface in Listing 2. I’d declare these methods in my base class as overridable but I wouldn’t bother adding any code to them. Adding these methods (commonly called "hooks") allows developers that inherit from my base class to add their own code in between steps in the process. If I had some idea what processing developers might do in these methods, I’d pass parameters to these methods to support those activities. I’d also grab the opportunity to rename my methods (especially, the unfortunately named NotifyShipping).

Listing 2: Process Method with Hooks
Public Sub ProcessOrder
  OnOrderProcessing
  If CheckCredit Then
    OnCustomerNotifying
    CustomerNotify
    OnCustomerNotifyed
    OnAccountingNotifying
    AccountingNotify
    OnAccountingNotified
    OnFullfillmentNotifying
    OrderFullfillmentNotify
    OnFullfillmentNotified
  End If
  OnOrderProcessed
End Sub

Public Overridable OnCustomerNotifying

End Sub 
'...more methods...

Now the client just instantiates the right process class (passing in the entity classes) and calls the ProcessOrder method. To help the client get the right process class, you might consider implementing the Factory Method pattern. You may recognize this pattern if you ever created a desktop application: My various On* methods correspond to events that are fired during the life of a form, for example.

As the number of variations increase, though, you may decide that the Template Method pattern shouldn’t be the solution to all of your problems. It might be easier, for some variations, to just let the client pick and choose which methods on the process class need to be called. Another option is to use the Decorator pattern that modifies functionality associated with a method in an additive way -- but that’s a different column.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube