Practical .NET

Chain of Responsibility and Adapting to Change in Complex Apps

As a company's problems continue to become more complicated, your code will become more complicated. Peter shows how refactoring code can lead you to better designs.

When I discuss design patterns, I often get the feeling that people think I leap straight to the right design, based on a moment of blinding insight. Nothing is further from the truth, as I'll demonstrate here. For example, in my last article, I showed how the Chain of Responsibility (CoR) design pattern provides a way for me to extend existing simple code to handle more complicated problems without rewriting the original code. I would get to the CoR pattern by evolution, not revolution.

In this article, you'll see how to build on the CoR pattern to handle increasingly complicated problems. I'll do this not by throwing out code, but by refactoring existing code, ideally without breaking existing clients. My overall goal, though, is to supply "enough engineering" to support the current problem without over-engineering a solution to some later problem that might never exist.

The Benefits of a Base Object
In my last column, I created a set of objects that each tried to retrieve a Customer object -- when an object couldn't retrieve a Customer object, it simply handed the problem over to the next object in the chain. My first problem: The number of objects in the chain has increased to three or four items, so there are distinct advantages in putting any common code in some base object. First, having a base object makes creating the next chain object easier to create by centralizing any common code. Second, if I ever need to make a change to all of the chain objects, I might be able to implement that change in the base object, rather than having to modify a large number of individual chain objects.

For my chain objects, the common code is the method that hands requests to the next object in the chain. Therefore, my base object will have a method that can only be called from inside a chain member and that will simplify creating the next item in the chain. My chain objects currently must also have a GetById method, so my base class will also include a public overridable GetById that individual chain objects can customize.

If you're using a Dependency Injection Manager like Unity or MEF, you might use it in the method that creates the next chain object. For this example, as you can see in Listing 1, I've used some reflection code that will instantiate a class when passed the class's Type object (in the C# version, the MustInherit and MustOverride keywords are equivalent to the abstract keyword):

Listing 1: A Base Class for Chain of Responsibility Objects
Public MustInherit Class CustomerRetrievalBase
  Public MustOverride Function GetById(custId As String) As Customer

  '...other repository methods...

  Protected Function GetNextObjectInChain(classType As Type) As CustomerRetrievalBase
    Dim rep As CustomerRetrievalBase
    rep = Activator.CreateInstance(classType)
    Return rep
  End Function
End Class

A new chain object would now look like Listing 2.

Listing 2: A Chain Object Built on a Base Class
Public Class CustomerRepository
  Inherits CustomerRetrievalBase
  Public Overrides Function GetById(custId As String) As Customer
    If custId(0) <> "C" Then
      Dim cust As Customer
      '...original code to retrieve Customer object...
      Return cust
    Else
      Dim rep As CustomerRetrievalBase
      rep = MyBase.GetNextObjectInChain(GetType(CustomCustomerRepository))
      Return rep.GetById(custId)
    End If
  End Function

Many developers, however, would boil the code in the Else block down to this single line:

Return MyBase.GetNextObjectInChain(GetType(CustomCustomerRepository)).GetById(custId)

Now, creating a new item for the chain consists of inheriting from the base class, then writing the code to retrieve and return a Customer object. When it's not possible to return a Customer class, the developer needs to call the base GetNextObjectInChain method passing the Type of the next object in the chain (and calling the appropriate methods on that object). I'll obviously have to rewrite any existing chain objects to use this base class, but I've made this change before the number of chain objects got so large as to be intimidating. I don't have to rewrite any client applications, though.

Starting the Chain
This plan would work … except for the first object in the chain, which -- right now -- is called directly by the client. I'd probably be happy with that design, though. If I need to ever add a new "first" object, I'll rewrite the client, which is hardly an onerous burden.

Having said that, there are two reasons I'd consider rewriting the client:

  • If rewriting/redeploying the client is going to be a major effort
  • If I thought multiple clients would be using the chain

In either of those cases, I'd probably create a "head of chain" object whose sole responsibility is to call the first object in the chain. With that design, if I ever need to add a new object at the start of the chain, I'd just rewrite the head object. That will save me updating multiple clients or redeploying the chain's single client.

Currently, my client calls the GetById method on the first object in the chain -- a class called CustomerCache. I would move that code into my head object to give a class like this:

Public Class CustomerRetrievalHandler
  Inherits CustomerRetrievalBase
  Public Overrides Function GetById(custId As String) As Customer
    Dim rep As CustomerCache
    rep = New CustomerCache
    Return rep.GetById("C123")
  End Function

My existing client (and any subsequent client) would now use this code to start the chain's processing regardless of what changes I need to make to start the chain:

Dim rep As CustomerRetrievalBase
rep = New CustomerRetrievalHandler
Dim cust As Customer
cust = rep.GetById("A123")

Handling Failure
At this point, if you're familiar with the Decorator pattern, the CoR pattern might sound familiar to you. The major difference between the two patterns is that the Decorator pattern always forwards every request to the object at the end of the chain; in the CoR pattern, any object in the chain may solve the problem without calling the next object in the chain.

That does point out a special issue with the CoR pattern: What happens if you reach the end of the chain without resolving the problem? To put it another way: What code do you put in the Else block for the object that has no further object to call? My personal opinion is that, if not resolving this request should bring any client using the chain to a shuddering halt, then you should raise an exception; if that's not the case -- if a client could continue processing -- you should just return null. A more sophisticated solution would be to return a new object with a property that holds the Customer object if one is found and another set of properties that describe the processing that occurred (what chain item finally processed the request, a success/failure code, the initial parameter passed to the head of the chain and so on).

Simplifying Creating the Chain
As long as there's only one short chain, I would be happy with my current solution, though. However, because each chain object is responsible for creating the next object in the chain, control over the creation of the chain is distributed throughout the chain. If the problem required even more options (that is, if the chain started getting longer), this "distributed creation" could make debugging errors more difficult -- I might have trouble figuring out which item in the chain actually had the error. Alternatively, if the number of clients increased, I might discover that I might need several different chains: some clients would require specific objects in the chain to be included (or omitted) that other clients would not.

In the face of either of these problems, I would want to centralize the logic for creating the chain into one place. To a certain extent, I'm following the single responsibility principle: Objects in the chain should only be responsible for handling the problem they were designed for, not for managing the chain. Fortunately, because all chain objects inherit from the same base class, I can make that change without breaking any of my existing clients or rewriting any of my existing chain objects.

If all of my clients are starting the chain by calling the "head of chain" object I created in my last article, I won't have to rewrite my clients, either. Effectively, I can have my head-of-chain object implement a version of the Factory Method pattern where a method that I call returns a correctly configured object.

My first step is to enhance my base chain object, which will let me support centralizing creating the chain without breaking any existing chain objects. In my base chain object, I add a property that will hold the next item in the chain:

Public Class CustomerRetrievalBase
  Public Property NextItemInChain As CustomerRetrievalBase

Next, I enhance my GetNextItemInChain method to create the next item in the chain only if that property is set to null or Nothing:

Protected Override Function GetNextObjectInChain(classType As Type) As CustomerRetrievalBase
  If Me.NextItemInChain Is Nothing Then  
    Dim rep As CustomerRetrievalBase
    rep = Activator.CreateInstance(classType)
    Return rep
  Else
    Return Me.NextItemInChain
  End If
End

My next step is to enhance my head-of-chain object to instantiate each item in the chain, setting that property as it does so. Listing 3 builds the chain that I've been using so far in these articles.

Listing 3: Building the Chain
Public Class CustomerRepositoryHandler
  Inherits CustomerRetrievalBase

  Public Override Function GetById(custId As String) As Customer
    Return BuildDefaultChain.GetById("C123")
  End Function
  Private Function BuildDefaultChain() As CustomerRetrievalBase
    Dim repStart As CustomerRetrievalBase
    Dim repNext As CustomerRetrievalBase

    repStart = New CustomerCache
    repNext = repStart.NextItemInChain
    repNext = New CustomerRepository
    repNext = repNext.NextItemInChain
    repNext.NextItemInChain = new CustomCustomerRepository 
    Return repStart
  End Function

If the code for creating chains grew increasingly complex, I might consider moving that code out of the head-of-chain class into its own class, which really would be an implementation of the Factory Method pattern. At this stage, though, I'd probably consider that over-engineering the solution (others would disagree).

Currently, my factory method builds the chain the same way every time. If, however, my chain requires some flexibility in its construction, I might need to include some If-Then logic in my factory method to dynamically build the chain based on the current environment. At that point, I would definitely switch over to having my GetById method return a more sophisticated object that holds the Customer object and other information, reporting on what processing actually took place.

No Free Lunch
I may not be done refactoring this solution yet, though.

While I'm now positioned to create multiple different chains and I've centralized the creation of the one chain I'm using, there's a potential performance issue here. In the original, distributed design, chain objects were only created if they were needed. In this centralized design, I'm creating all of the objects in the chain, many of which will never be used. As long as the cost of creating a chain object is low, I probably won't care. If I do care, I might turn my head-of-chain class into a kind of facade class that actively calls the chain objects on behalf of the client, working its way through the chain as required.

Alternatively, I might have the head-of-chain class cache the chains rather than recreate them (in which case, I'd want to make check if it was possible for multiple clients to call a chain simultaneously and, if so, confirm that my classes are thread-safe). If I added that functionality to the head-of-chain class, I would definitely move the chain creation out into a separate factory class.

Because I've been refactoring existing code and trying not to break existing clients, I've also created a documentation problem. Existing chain objects are passing the "next object in the chain" to the GetNextItemInChain method. However, with my latest changes, that "next object in the chain" is ignored if my head-of-chain class has set the NextItemInChain property. The next programmer, looking at a call to a method named GetNextItemInChain and not being aware of the code in the base object, might not realize that the value passed to GetNextItemInChain doesn't always matter.

And I do realize that there are more objects at the end of this column than there were at the start … but only because I'm handling a more complicated problem than I started with. I'm not quite done yet because, it turns out, the next application the company wants to build is more complex than the CoR design pattern can handle all by itself. I'll talk about that in my next column (and also address a simplifying assumption I've been ignoring) with the Data Transfer Object and Command patterns.

comments powered by Disqus
Most   Popular
Upcoming Events

.NET Insight

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.