Field Notes: Using the Strategy Pattern
I had been using the e-commerce example in my pattern classes when someone asked, “Are you aware that in the U.K. people over a certain age don’t get taxed on food?” I wasn’t aware of this, and the interface for the CalcTax object did not handle this case. I could handle this in at least one of three ways:
- Pass the age of the Customer to the CalcTax object and use it if needed.
- Be more general by passing the Customer object itself and querying it if needed.
- Be more general still by passing a reference to the SalesOrder object (that is, this) and letting the CalcTax object query it.
Although it is true I have to modify the SalesOrder and CalcTax classes to handle this case, it is clear how to do this. I am not likely to introduce a problem because of this.
Technically, the Strategy pattern is about encapsulating algorithms. In practice, however, I have found that it can be used for encapsulating virtually any kind of rule. In general, when I am doing analysis and I hear about applying different business rules at different times, I consider the possibility of a Strategy pattern handling this variation for me.
The Strategy pattern requires that the algorithms (business rules) being encapsulated now lie outside of the class that is using them (the Context). This means that the information needed by the strategies must either be passed to them or obtained in some other manner.
The Strategy pattern simplifies unit testing because each algorithm is in its own class and can be tested through its interface alone. If the algorithms are not pulled out, as they are in the Strategy pattern, any coupling between the context and the strategies makes testing more difficult. For example, you may have preconditions before you can instantiate a context object. Or the context may supply some of what becomes the strategy through a protected data member. Testing is even further simplified if several different families of algorithms coexist. (That is, several Strategy patterns are present, which is typically the case.) This is because by using Strategy patterns the developer does not need to worry about interactions caused by coupling with the context. That is, we should be able to test each algorithm independently and not worry about all the combinations possible.
In the sales order example earlier, I had the TaskController pass the strategy object to the SalesOrder object each time it was needed. A little reflection would tell me that unless I were reusing the sales order object for different customers, I would always use the same strategy object for any one particular SalesOrder object. A variation of the Strategy pattern I often see is to assign the strategy object to the context in the Strategy pattern (in this case, my SalesOrder object) in the context’s constructor. Then any method that needs to reference it can, without requiring it be passed in. However, because the context still doesn’t know what particular type of strategy object it has, the power of the pattern is still maintained. This can be done if which particular strategy object is needed is known at the time the context object is constructed.
I have sometimes had students complain that the Strategy pattern causes them to make a number of additional classes. Although I don’t believe this is a real problem, I have done a few things to minimize this when I have control of all the strategies. In this situation, if I am using C++, I might have the abstract strategy header file contain all the header files for the concrete strategies. I also have the abstract strategy cpp file contain the code for the concrete strategies. If I am using Java, I use inner classes in the abstract strategy class to contain all the concrete strategies. I do not do this if I do not have control over all the strategies; that is, if other programmers need to implement their own algorithms.