Code Notes: Refactoring with the Strategy Pattern
Technology changes fast. With it, new tools and frameworks are continuously becoming available, making it nearly impossible to keep on top of it all. The only defense against this profusion of technology is to have good fundamental software design skills. That starts with being familiar with design patterns. If you can recognize these, you’ll find they’re often transferable among a vast array of technologies.
Let’s start with the basics. Design patterns are specific implementations of one or more of the S.O.L.I.D principles that are used to solve common problems. Inspired by Daugherty’s own John Gobble’s evening class on Pragmatic Middle-Tier Design, I recently used the strategy pattern at a client site to convert what was becoming an increasingly kludgy section of code into a clear and concise set of modules. This makes it more easily maintained and extended over time. Here’s how I did it.
The project entailed a set of web services that would:
- Return the appropriate agreement for a customer based on his order
- Support the customer acceptance of the agreement,
- Dispatch the “agreement accepted” information to the appropriate downstream systems.
The existing system was in place, but the project was to expand it to accept different types of agreements, each of which interfaced with different systems under unique conditions.
Figure 1 is an extremely simplified and generalized version of the code snippet that red flagged a need for a refactoring effort.
I noticed here a couple of things:
- The systems that a particular agreement was interfacing with was not quickly apparent.
- This routine would just get messier as new agreement types were added.
I first built a spreadsheet to get an understanding of which agreements were supposed to go where (once again a simplified version).
I noticed the “agreement accepted” information would be sent to System 1, 2, and 3. For each system there were different flavors (concrete classes – i.e. System1A, System1B, System1None) involved. First, I created an abstract class that had three properties of an interface type, one for each system. Then, I added a set of abstract methods that forced any derived classes to assign a concrete instance for each system.
To then take a look at a particular agreement manager class, we would have something like this:
Then, I created the concrete classes for each of the interface systems. The UML for the whole set is shown here.
After the refactoring effort was completed, the original snippet of code was boiled down to the following.
The factory method shown in Figure 3 was implemented using Ninject named bindings. It determines which concrete agreement manager class to return based on the name of the agreement (I won’t go into that here, but I thought it was worth mentioning).
The strategy pattern in this example allows us to easily maintain where and when data should be sent for a particular agreement without having to change the overall process. It’s a powerful pattern that I might have missed had I not attended John’s class at Daugherty. Thanks John!