Being Flexible With Responsibility Chains

The key word behind using third-party libraries and components is reuse. Before setting out to write software from scratch, people usually browse for ready to use solutions for their problems. This helps reduce development time and allocate resources to other areas.

Sometimes, we find that some of our needs are better served by a combination of third-party components instead of a single one. Especially when relying on an online service to provide some core functionality in our app, we need to make sure that a backup mechanism is in place and ready to react when the service is not responding.

We will illustrate the above situations better with a real world example taken from an application developed a few years ago. In that app, at some point end users were offered the choice to pay for a product in the currency of their choice.1

For the exchange feature, the app communicated with a Restful service to get the rates. Everything was running smoothly, until one day it didn’t work at all. When asking to pay in a currency other than the default, customers were met with “Sorry, this feature is not available right now. Thank you!”. What had happened? The exchange rate API provider had simply stopped …. providing the service.

In the meantime, the company owners had decided that the monthly amount they had to pay for the conversion service was high and they would rather go with the free plans many providers offered (usually with a cap imposed on the number of requests allowed for free per month). 2

 

The Problem

So, the problem is twofold:

  1. We need to have at least one backup service, so that when the default is not available the feature is still available to the end user. In other words, we need to make sure that a request gets handled but we don’t really care (from the client’s point of view) which service is going to handle it.
  2. We want to drop the cost of the feature to zero (or as close to zero as possible).3

The situation before the problems came up, looked like this:

Chain_Of_Responsibility_Before

Our view controller class knew about the exchange rate api and communicated with it directly. When the service stopped working, the code specifically targeted to that particular service had to be replaced with code talking to a new api. This tactic of course is not resistant to change, since the same thing would happen if WHEN the new api would stop working. 4

func Double requestRates(fromCurrency: String, toCurrency String)
{
    //this line as well as any reference to "exchangeRateService"
    //has to be modified every time we need to replace the service
    //provider
    return exchangeRateService.requestRates(fromCurrency, toCurrency);
}

This could of course be avoided by using an abstraction for the service instead of writing code targeted to one specific service only. Then, for switching to a new service all we would have to do is wrap the new service inside an Adapter.

While this solution is valid, it doesn’t take into account the possibility that the service won’t be available when we need it (i.e. we would have to recompile the source code and issue an update, not exactly an immediate solution; especially if the problem occurs during non working hours). There is a huge number of cases when we need a request to be processed no matter what, by at least one service provider/class/whatever like in the exchange rate scenario.5

One solution would be to include conditional logic in our application’s code that routes the requests based on which service is cheaper, on the number of ‘free’ requests available for use by the app for a particular service, on whether a service is online/available at the moment of the request etc.

This way though, the view controller code is vulnerable to modification, every time a new service is introduced or a business rule dictates changes, we have to dive in the view controller and modify it. Trying to make one’s way through dozens of nested if-else statements is not only error prone, but tedious and tiresome as well.

 

The Solution

 

Chain Of Responsibility Design Pattern

 

In the above diagram, the client (CheckoutViewController) holds a reference to an ExchangeRateService object and directs all requests that have to do with rates there.

Depending on implementation specifics, the ExchangeRateService then either asks for the “right” service to do the work or simply plays the role of the first service in the chain (MainService), which either handles the request or passes it on to the next “backup” service in the chain. 6

The interesting part is the recursive aggregation (“successor” in the diagram) which means that every concrete service holds a reference to the next service in the chain. This way, if the MainService cannot for whatever reason handle the request, it then calls the returnRate method on its successor and this keeps going until the request gets handled.7

You can think of the Chain Of Responsibility as a pipeline, the client object provides a request and is entirely oblivious to the whole chaining action taking place under the hood:

 

Chain Of Responsibility - Pipeline Example

 

By using this pattern we have addressed both problems:

  1. CheckoutViewController asks for the exchange rate but does not know which service actually handles the request. This way, we can add and remove services based on business decisions without modifying the core app.
  2. We can add a threshold for every service depending on how many more free requests are left. If for example MainService only allows for 10000 requests per month, we can configure MainService to forward the requests to its successor in the chain when we run out of free requests. This is obviously really useful when we are asked for solutions on cutting costs (and that’s a really popular request, especially among people in managerial positions).

 

What If No Service Can Handle The Request?

It is entirely possible that even with a chain mechanism in place a request can go unhandled (maybe for example because of lack of internet connection on the client/app side).

To cover this scenario, the responsibility falls on the developer to make sure that the “last” service in the chain has a way of returning an error message to the client instead of trying to delegate the request to its (nonexistent aka nil) successor.8

 

Where Should The Chain Be Created?

Ideally, chains would be created in the application’s composition root (i.e. the AppDelegate) and injected in the view-controllers/client-classes using it to:

  1. avoid applying the chain creation weight on the client classes and,
  2. maintain a centralised point of change for when we need to add or delete handlers to/from the chain.

 

You Have Been Using This Pattern For Years

Both Cocoa and our beloved Cocoa Touch (as well as most frameworks that handle events) make heavy use of the Chain Of Responsibility pattern.

When a user taps on the screen for example, a UIEvent gets propagated through the view hierarchy until it gets handled by whoever knows how to or is interested in handling it.

All views (UIView) and view controllers (UIViewController) as well as windows (UIWindow) and even the application object itself (UIApplication) inherit (in)directly from UIResponder.

Words like “nextResponder”, “isNextResponder”, “becomeFirstResponder” are spread throughout cocoa projects. “Responder” is essentially Apple’s way of saying “Handler” in a chain. They have actually given this event-handling structure the name Responder Chain.

 

An Object Oriented if-then-else statement

Many people think of the Chain Of Responsibility pattern as an Object-Oriented if-then-else statement or linked-list and that is totally valid. The pattern really is indeed a more powerful and flexible alternative to handling conditional logic and is essentially a linked-list customised for a specific purpose.

 

How Should We Structure Requests?

Requests range from simple method calls with primitive type parameters to complex structured hierarchies wrapped inside objects.

A good approach, from an extensibility’s point of view, is to use a single Request base class as the parameter to a generic handle method and provide different implementations of Request objects via subclassing. This allows infinite options on what an actual request may be. Then, each Handler class can check (via isMemberOfClass for example) if it can handle the particular type of request passed and proceed with handling the request or passing it on to the next Handler in the chain.

For example:

func handle(request: Request)
{
    if(request.isMemberOfClass(ExchangeRateRequest))
    {
        //do something meaningful
    }
    else
    {
        successor.handle(request)
    }
}

This solution is essentially a combination of the Chain Of Responsibility and Command, another GOF pattern. This pattern is essentially baked into Objective-C thanks to the language’s dynamic nature that gives full flexibility and power when it comes to messaging and forwarding requests (using tools like forwardInvocation: for example). What this means is that essentially the language does not need Command.

Swift has (at least temporarily) taken this power away from developers and many people have been really vocal about how they don’t like this.9 This feature is part of the Objective-C runtime so people will probably find ways to work around the limitation or maybe Apple will decide to expose it (in)directly in some way after all.

Command is also very easy to implement in Swift thanks to closures. More on this subject will be discussed in a future article.

 

Summing Up

The Chain Of Responsibility pattern:

  • Promotes loose coupling between the sender and receiver of a request, allowing more than one potential objects to handle a request.
  • Is very useful for fire-and-forget type of requests.
  • Provides backup functionality for requests targeted towards web services that may not respond within an acceptable time frame.

Things to consider:

  • A plan should be in place for when a request cannot be handled by any object in the chain (fail gracefully).
  • Depending on the context, the chain superclass could play a role in deciding the flow of the request through the chain or only provide default implementation(s) for the “handle” method(s).
  • Request representation is crucial for the extensibility of the code base. Plan with potential future changes and needs in mind.

 

There is going to be a section on this website dedicated to the use of patterns throughout the Cocoa Touch framework where the use of the Chain Of Responsibility Pattern is going to be discussed in detail. Keep up to date by subscribing or following us on twitter.

 

Recommended Reading

Books:

Design Patterns: Elements of Reusable Object-Oriented Software (The Gang Of Four Book)

Head First Design Patterns (In my experience, the best introductory book on Design Patterns)

Pro Objective-C Design Patterns for iOS

Agile Principles, Patterns, and Practices in C# (Uncle Bob’s seminal book, a must read)

Articles:

Discussion On Chain Of Responsibility


  1. The currencies supported were USD, Euro, and GBP. Also, this was not an iOS app which goes to show that design patterns and object-oriented principles can be applied and transferred to most technologies rather effortlessly.

  2. They were also willing to pay good money to get rid of that monthly expense, so I decided to take the job.

  3. This is definitely not the main motivation behind the pattern. It’s more of a bonus.

  4. Making decisions based on the worst case scenario is usually the safest way to go. Won’t help with your popularity among your ninja “brute-forcing-everything” programmer peers though.

  5. Health and banking apps come to mind.

  6. In the first case, one could argue that we are closer to a Strategy or a Mediator than a chain but that’s not really true since the ExchangeRateService will still go through all the available services sequentially until it finds the one that can handle the request.

  7. Depending on the use case, a request could go through the whole chain or part of the chain even when the concrete classes successfully handle requests. You usually see this in event-driven technologies and error/exception handling solutions provided by frameworks.

  8. Note that nil or “ignore behaviour” is of course acceptable if it is expected as a potential result.

  9. Swift in general has been criticized as a mixed-bag of popular features from other languages without a true vision or goal. I tend to disagree.