Strategy Pattern vs. Case Statement

Source: comp.object
Date: 03-Mar-99

Related Sites


------------------------------

o-< Problem: What are the advantages of using the Strategy Pattern over a simple if-else-if chain or case statement?


---------------

o-< Tim Ottinger wrote:

  1. The code of the strategy's containing class doesn't have to be changed when a new strategy is added.
  2. Once you've isolated the routine, things like Decorator become much easier.
  3. Polymorphic dispatch coding is very simple. Case statements can become very complex.
  4. Each strategy can have local variables and remember calls across time. A switch/case element requires tricks like:
    case XXX:
        { // Open a scope to allow local vars
            int x;
            static double last_answer; // Hidden memory
        }
        break;
  5. I've accidentally left 'break' off of switch/case statements, but have never made any similar mistake with strategies.
  6. You don't pay for the strategies you don't use. Every switch/case target is hard-coded into the switch/case. You have to link in all the support, even if you only use one option.
  7. Flags are evil. ;-)
  8. If you have a multifaceted abstraction, strategies allow you to mix-and-match cleanly. Using switch/case means you have to edit and bloat the code. Using inheritance means a cartesian explosion of classes -- Strategy is just good Dependency Management.
[An illustration of this last point:]

Switch/case:

switch(pay_type) {
case Hourly:
    // calc pay
    switch(pay_delivery_type) {
    case DirectDeposit:
        // whatever
        break;
    case CheckMailedToEmployee:
        // whatever
        break;
    }
    case Salaried:
        switch(pay_delivery_type) {
        case DirectDeposit:
            // whatever
            break;
        case CheckMailedToEmployee:
            // whatever
            break;
        }
This is particularly ugly and will be hard to maintain. Every new pay delivery type and every new pay calc type required this very ugly code to be maintained, and (as written) you have to add every pay delivery method in many places (once per pay calc type).

Admittedly this could be done better with one switch/case on pay calc types, followed by one on pay delivery type, but leaving off the break and interleaving the conditions are the two most common errors I see in switch case -- after all, the statement grows large, and people become impatient with reading.

Also, there is some chance that how you prepare the check may be dependent on the details of how you calculate the pay, though we try to eliminate any such dependency. In a switch/case statement, it's no uglier than the code looks ALREADY, so you end up with a switch on pay calc types, followed by a switch on pay delivery type codes, where some of the pay delivery types will include another switch on pay calc types... you can see the code becoming increasing fragile.

Inheritance:

+----------+
| Employee |
+----------+
|/CalcPay/ |
|/Pay/     |
+----------+
    /_\
     |------------------+---------------------+
     |                  |                     |
+----------+     +-------------+       +------------+
|  Hourly  |     | PayByDeposit|       |  Salaried  |
| employee |     |  Employee   |       |   Employee |
+----------+     +-------------+       +------------+
| CalcPay  |     | Pay         |       | CalcPay    |
+----------+     +-------------+       +------------+
   /_\                 /_\
    |                   |
    +-------------------+
             |
      +---------------+
      | DirectDeposit |
      |   Hourly      |
      |   Employee    |
      +---------------+
YIKES! A cartesian explosion of leaf classes, all of which have the deadly *inheritance diamond*!!!! It also approximates a routinely poorly written switch/case statement, where the code is all compounded and mixed all over, only in this case it's the inheritance that does all the mixing. It's very messy, very fragile, and hard to extend.

The derived class has to deal with the differences in pay generation based on different types perhaps, and it would be scribbled right into the derived class methods. At least they're kept out of the higher-level (non-leaf) classes.

Strategy:

+-----------+      +----------+      +-------------+
|/Pay Calc  |<-----| Employee |------|/PayDelivery |
| Strategy/ |      |          |      |   Strategy/ |
+-----------+      +----------+      +-------------+
    /_\                                  /_\
     |                                    |
+------------+                       +------------+
| Hourly Pay |                       |   Direct   |
| Strategy   |                       |  Deposit   |
+------------+                       +------------+
Here things are much more simple. Admittedly, it approximates a very well written switch/case statement (with the 7 OTHER good qualities). It is simple, it is clear, and it allows these variable aspects (algorithms) to be treated uniformly and extended seamlessly.

A dependency on the pay calc method can be handled by the bidirectional linkage to employee, and methods can be created in the pay calc strategy for it.

Good use of polymorphism, good use of Strategy, clean separation of concerns.


------------------------------

o-< More Info:

Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides,
Design Patterns -- Elements of Reusable Object-Oriented Software

Excerpt from the GOF, The Strategy Pattern

Kulathu Sarma, Applying Strategy Pattern in C++ Applications

The Decorator Pattern

Patterns Home Page


------------------------------