Strategy Pattern: A Generic Programming Perspective

A very interesting Java newsletter1 was the starting point of this article. After a swift presentation of a classic Strategy Pattern2, a TaxPayer Context paired with a TaxStrategy hierarchy, the newsletter tries to answer a more difficult problem: What happens if the Context becomes a hierarchy? How do you keep track of the real type of the TaxPayer inside TaxStrategy without downcasting?

You'll find several answers in this article—from a Generic Programming perspective.

What's the Buzz?

Consider the popular TaxPayer, paying unpopular taxes as dictated by TaxStrategies3:

In the real world, there is a family of TaxPayers, different enough to be hard to abstract behind an unique interface. Most probably, you'll find TaxPayers in a loose hierarchy (if any at all)—Employees, Companies, a.s.o. TaxStrategy has to take this diversity into account—companies are not taxed the same way as their employees and employees expose different attributes than companies.

You have to make TaxStrategy aware of the different TaxPayers. One way to accomplish this is to pass TaxPayer as a parameter 1, 2.

One obvious route is to use the Visitor pattern that offers double dispatching out of the box; this will be investigated in a future article4.

On the other hand, Reference 1 describes a non-trivial solution based on generics. You'll try to match that implementation and even to improve it by using C++ templates and generic programming techniques. This has been done in a couple of steps, presented below.

Creating an Equivalent Solution (crtp.h)

A C++ replica of Reference 1 can be easily constructed by using the CRTP (The Curiously Recurring Template Pattern5). The payTax() mechanism is factored out in a TaxPayer base class:

This works in Java due to raw types (but take into account that "The use of raw types in code written after the introduction of genericity into the Java programming language is discouraged. According to the Java Language Specification, it is possible that future versions of the Java programming language will disallow the use of raw types"6) , but not in C++.

TaxStrategy should be a variation point (TaxStrategies of different types should be pluggable in taxPayer). This is not possible in the previous example, but is possible in Reference 1 where TaxStrategy is an interface allowing further customization in derived classes.

TaxStrategy<T> are independent, unrelated classes. TaxStrategy should be seen as a family of algorithms more than anything else. (It can be a functor or a simple function as well.)

This brings you on par with Reference 1 in terms of functionality, but the solution is suboptimal. As noted in Reference 7: "Operation polymorphism (keyword virtual in C++) is excluded [in parametric polymorphism; in other words, (compile-time) genericity] because dynamic binding is too expensive. Or, saying it differently, abstract methods are forbidden."

Note: This is not always possible when mixing the two types of polymorphism or is not desirable—it adds complexity.