Chain of Responsibility
Video transcript & code
In the last episode, we learned how to extend the command pattern with new undo-ability powers. Chris has a lot more to teach us about the command pattern. But since this miniseries is more of a sampler platter than a main course, we’re going to switch gears today and move on to the chain of responsibility pattern instead. The chain of responsibility pattern is a great way to eliminate conditionals from code, and the video you’re about to see is both a fun and very practical application of it.
Just a reminder: the Compendious Thunks code examples are in the Dart programming language. It’s a familiar enough language that you probably won’t have any trouble following along. But if you want a quick orientation, jump back to episode #461 for a quick 5-minute introduction that will show you everything you need to know about Dart.
Your code is out to get you.
It's not paranoia on your part. Your code lurks in the dark places that ghosts, demons and sprites infest and it conspires against you. It exploits weakness whenever it can. And while it doesn't take much, its preferred source of dark powers are the conditionals that we all instinctively create.
There are talismans that can aid us in our struggle. One such charm is named the CHAIN OF RESPONSIBILITY.
[embed_dartpad id="da8d637a160a9403643ec00614a58133" title="We start with some ugly conditionals..."][/embed_dartpad]
The chain of responsibility is most useful when related objects are all trying to handle the same request. It's a little hard to see here due to a blight of conditionals, but we have a series of image objects (JPEG, GIF, PNG) that are all trying to import an image. Each will extract and store meta data from the image and maybe create a standard-use thumbnail, but how depends on which kind of image. And so we have if statements to use the
JpegImage to import JPEG images, the
GifImage to import GIF images, and so on.
Now, this is working code. To a point. The conditionals properly send a file ending with
JpegImage. A file ending with
.gif goes through
GifImage. The unknown image doesn't get picked up by anything so it gets handled by
UnknownImage (likely storing the raw date without meta info or thumbnail). But the
.jpeg image and the JPEG image with the wrong extension are both mishandled.
Already, dark code forces thrill over mischief making possibilities. It's hard to discern the true intent of this code. If we want to support
.jpeg extensions, are we to add more to the conditional? If we want to inspect contents of files, do we make the conditional more dense still? If we want to support more image types, do we make this conditional chain longer?
While code gremlins are giddy for us to do just that, level-headed minds already cast about for better ways.
All can agree that the image classes are better at deciding if they can import a particular file. So let's start with a
canImportgetter -- false by default, but true for the appropriate cases in the concrete classes.
Those cases are true if the extension matches. The handler of last-resort,
UnknownImage will always return true. Great, but how do apply
canImport in practice?
The nifty idea of the chain of responsibility comes from the recognition that the different handlers (
PngImage, etc.) are a... chain. A chain of request handlers. If one link in the chain cannot handle the request -- if one image handler cannot handle the import request -- then the next handler in the chain gets a chance.
Phrased like that, we can push the requests down into the handler method --
import(). If the current handler cannot handle the request, then ask the next handler to do so. If it cannot, then it asks the next handler to try. And so on until the request is handled.
For that, we need the chain a little more explicit. The
nextImporter property informs each concrete handler class what the next handler will be. Following the same order from the original code,
nextImporter will be
GifImage's will be
PngImage's will be
What's really cool about this is that all of this original application is now extraneous. The conditionals and the structure of the chain are all contained in the handler classes. So we can simply create the first item in the chain, tell it to import, and... everything still works! The
.gif image is still handled by
.jpg file is still handled by
JpegImage. The unknown image still falls to
In addition to brilliantly clear application code, it's obvious where we support
.jpeg extensions -- in
canImport getter method. Which is also where we can inspect the initial contents of the file as well.
One minor bit of cleanup that we'll undertake here is the class used by the application. It's a little weird that the application needs to know that the chain's entry point is
JpegImage so we create
ImageImporter to serve that function.
[embed_dartpad id="587c4ec7fdbb091932819cd541fb5990" title="Beautiful conditional code?"][/embed_dartpad]
Look, our code is still out to get us, but this is a nice tool to make the job harder. Application code is unaware of conditionals -- it's just importing images. The conditionals and chains are much more comfortable in the classes that should hold them.
Code demons will have to look elsewhere as this code is readable today and should be just as readable in 6 months by us or co-workers. Plus, it's familiar to any web programmer that ever wrote an event handler -- if one handler can't handle an event, it bubbles on over to the next handler.
That covers chain of responsibility basics, but there are nifty ways to tweak the approach. The full series will look at a more robust successor chain, handling more than one request, automatic request forwarding and, of course, clean code approaches.