Monday, January 22, 2018

Chain of Responsibility Pattern

When I first started programming I read a book about design patterns called "Head First Design Patterns."  Let me tell you, it was mostly over my head.  Don't get me wrong.  I definitely recommend the book.  But for a beginner, it is not.  The thing is, you're warned early on as a programmer that design patterns are seductive.  You'll start trying to apply the pattern, even when a pattern isn't really necessary.  I did this a lot, of course, because using a design pattern makes you feel like a real programmer.  You feel like a wizard looking through his bookshelves for just the right spell to change your friend back into his human form after being transformed into a polka dotted flamingo by the local witch.

Seriously, Internet?  Seriously?

The thing is, the problem comes first, and the design pattern usually needs to be tweaked before it's actually useful for your specific problem.  Well today, I finally used a pattern successfully at work (much to the relief of my boss) and I think my design will stand the test of time.  My problem was simple: an approval process.  For our company, we need a process to be able to move documents around.  Say you have Billy in Engineering.  Billy needs to look at the specs of the car before passing off the specs to Mike, the machinist.  Mike will look at the specs, start hammering and welding to get the metal all in place, and then he'll send his truck full of parts to the factory where Leila the foreman will receive the materials.

So at first blush, I thought that a simple linked list would be able to handle this scenario.  Each link in the chain would just be an employee.  The list would know which direction it was going, and that would be that.  No need for a design pattern.  However, I thought about the problem some more and came up with scenarios that may break my plan.


What if Leila the foreman, ever so meticulous and exacting in standards, says that the pipes she received are the wrong size?  How can she kick the process back to Mike?  What if it needs to be kicked back all the way to Billy in Engineering?  How would the process know to kick something three steps back?  Even worse, what if there is more than one gatekeeper?  What if after Leila receives the parts, she must report to Andrew the accountant, Mayvin from documentation who needs to make sure her lovely Excel spreadsheets are filled with parts information, and Ezekiel the shift manager who has to line up guys to start building the machine.  If and when all 3 of these people are finished is when the process moves on to Gabriel the head manager, calmly sipping coffee in his office.

My linked list wouldn't necessarily account for that, nor would it account if there were stipulations for each step in the process.  Wouldn't it be convenient if the step in the process that had to do with Mayvin knew that she needed to upload her Excel sheet of the spare parts list?  Then, the Excel file could be available to people later on in the process.  Mayvin doesn't need to know exactly who is looking at her Excel file, but at least it's available in case Justin the line man working on the plant floor needs to know how much a sprocket is running these days.

So, to lay out my problem, what I need is:

1. A way to separate each part of the process from the whole.  That way, when I create a brand new process, I'm just moving pieces around or creating new ones.  It's like letter blocks. You can take the letters T-E-A-M and make them M-E-A-T.
2. Having multiple gatekeepers in a rung.  Heck, why not multiple rungs?  I could possibly have some complex processes.  Maybe there are two different branches that are going and then they meet at a certain point 5 steps down the line?
3. Keep track of the overall progress.  Since I can have a process that can be simple or short, I cannot hard code the actual process.  I don't want to lock it in either.  What if later on down the line, while the process is still going, someone makes an alteration?  What happens if poor Ezekiel gets fired Christmas day?
4. The process should be reversible.  There should be a way to back up the process at any point in case something changed.
5. A flexible way to handle requirements at a given step.  The minimum would need the user to "okay" that their step has been handled, but there should be a gentle reminder for them as to what they should be doing, or to make files or info available to people later down the chain.

Tall order.  However, I believe I hit pay dirt.  First off, I think the problem lends itself to a tree data structure with each "Approver" as a node in the tree.  How to handle the actual approval process?



Welcome to the Chain of Responsibility pattern.

Well, a version of it any way.  In the original design pattern, it's a way of separating the thing that is making a request from the objects that handle it.  That means if I want to buy a fancy new office chair, and I have to ask purchasing for approval, I send my request to a handler.  The handler will ask the concrete handlers what to do.  My chair is $100.  The first concrete handler (let's say the secretary) only deals with purchases less than 50 dollars because that's what's in petty cash.  She'll call her manager, and he'll deal with the purchase of my expensive office chair.  The request gets kicked down the line until someone deals with it.  I decided for my workflow, I'd use this pattern as a form of inspiration.

Each person in the chain is an "Approver."  They know the previous person in the chain (0 if there they are at the head) and they know the next person in the chain.  After all, Billy just needs to know to let Mike know he's done.  Past that is no concern of his.  The approver's know what they have to do before they can declare themselves "approved."  The Approval Process object is quite simple.  It will instantiate all of the approvers that belong to it.  It doesn't know what order they go in, and doesn't need to.  It can iterate through all of the approvers that belong to it, and ask them if they are done.

The Approval Process object is now highly flexible because you can remove or add approvers at will.  The process has no idea in what order they go in, so none of that is hard coded.  All it needs to do is get them together.  It can have a function to get all of the approvers in whatever order they are in, using their ids to create a tree, and then ask them if they are complete.  The approvers know who their parent node and child node is, and I can get all nodes that share parent and child, so I can get a tier and separate out branches if this is a multi branch process.

I decided to decouple it even more, so that the requirements for an approver were decoupled from the approver itself.  That way, the requirement for each approver can be tweaked and edited without having to make a new object.  It could even be reused.

I know, this isn't quite chain of responsibility, but the design got me thinking along the same lines, I think it's flexible enough it can be used for anything from purchase order requests (which would totally be chain of command), to workflow for a major project.  Feeling good about my life as a coder today.

No comments:

Post a Comment