The STRATEGY pattern solves the problem of inverting the dependencies of the generic algorithm and the detailed implementation in a very different way. Consider once again the pattern-abusing Application problem. Rather than placing the generic application algorithm into an abstract base class, we place it into a concrete class named ApplicationRunner. We define the abstract methods that the generic algorithm must call within an interface named Application. We derive FtoCStrategy from this interface and pass it into the ApplicationRunner. ApplicationRunner then delegates to this interface. See Figure 22-2 and Listings 22-8 through 22-10. Figure 22-2. STRATEGY structure of the Application algorithm
It should be clear that this structure has both benefits and costs over the TEMPLATE METHOD structure. STRATEGY involves more total classes and more indirection than TEMPLATE METHOD. The delegation pointer within ApplicationRunner incurs a slightly higher cost in terms of runtime and data space than inheritance would. On the other hand, if we had many different applications to run, we could reuse the ApplicationRunner instance and pass in many different implementations of Application, thereby reducing the code space overhead. None of these costs and benefits are overriding. In most cases, none of them matters in the slightest. In the typical case, the most worrisome is the extra class needed by the STRATEGY pattern. However, there is more to consider. Consider an implementation of the bubble sort that uses the STRATEGY pattern. See Listings 22-11 through 22-13. Listing 22-8. ApplicationRunner.cs
Listing 22-9. Application.cs
Listing 22-10. FtoCStrategy.cs
Listing 22-11. BubbleSorter.cs
Listing 22-12. SortHandler.cs
Listing 22-13. IntSortHandler.cs
Note that the IntSortHandler class knows nothing whatever of the BubbleSorter, having no dependency whatever on the bubble sort implementation. This is not the case with the TEMPLATE METHOD pattern. Look back at Listing 22-6, and you can see that the IntBubbleSorter depended directly on BubbleSorter, the class that contains the bubble sort algorithm. The TEMPLATE METHOD approach partially violates DIP. The implementation of the Swap and OutOfOrder methods depends directly on the bubble sort algorithm. The STRATEGY approach contains no such dependency. Thus, we can use the IntSortHandler with Sorter implementations other than BubbleSorter. For example, we can create a variation of the bubble sort that terminates early if a pass through the array finds it in order. (See Figure 22-14.) QuickBubbleSorter can also use IntSortHandler or any other class derived from SortHandler. Listing 22-14. QuickBubbleSorter.cs
Thus, the STRATEGY pattern provides one extra benefit over the TEMPLATE METHOD pattern. Whereas the TEMPLATE METHOD pattern allows a generic algorithm to manipulate many possible detailed implementations, the STRATEGY pattern, by fully conforming to DIP, additionally allows each detailed implementation to be manipulated by many different generic algorithms. |