Strategy Pattern vs. Case StatementSource: comp.objectDate: 03-Mar-99
Problem: What are the advantages of using the Strategy Pattern over a simple if-else-if chain or case statement?
Tim Ottinger wrote:
Switch/case: 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).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; } 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: 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.+----------+ | Employee | +----------+ |/CalcPay/ | |/Pay/ | +----------+ /_\ |------------------+---------------------+ | | | +----------+ +-------------+ +------------+ | Hourly | | PayByDeposit| | Salaried | | employee | | Employee | | Employee | +----------+ +-------------+ +------------+ | CalcPay | | Pay | | CalcPay | +----------+ +-------------+ +------------+ /_\ /_\ | | +-------------------+ | +---------------+ | DirectDeposit | | Hourly | | Employee | +---------------+ 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: 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.+-----------+ +----------+ +-------------+ |/Pay Calc |<-----| Employee |------|/PayDelivery | | Strategy/ | | | | Strategy/ | +-----------+ +----------+ +-------------+ /_\ /_\ | | +------------+ +------------+ | Hourly Pay | | Direct | | Strategy | | Deposit | +------------+ +------------+ 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. More Info:
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Excerpt from the GOF, The Strategy Pattern Kulathu Sarma, Applying Strategy Pattern in C++ Applications
|