Why is it beneficial to use the strategy pattern if you can just write your code in if/then cases?

For example: I have a TaxPayer class, and one of its methods calculates the taxes using different algorithms. So why can't it have if/then cases and figure out what algorithm to use in that method, instead of using the strategy pattern? Also, why can't you just implement a separate method for each algorithm in the TaxPayer class?

Also, what does it mean for the algorithm to change at runtime?

有帮助吗?

解决方案

For one thing, big clumps of if/else blocks are not easily testable. Each new "branch" adds another execution path and thus increases the cyclomatic complexity. If you want to test your code thoroughly, you'd have to cover all execution paths, and each condition would require you to write at least one more test (assuming you write small, focused tests). On the other hand, classes which implement strategies typically expose just 1 public method, which is easy to test.

So, with nested if/else you'll end up with many tests for a single part of your code, while with Strategy you'll have few tests for each of multiple simpler strategies. With the latter, it's easy to have better coverage, because it's harder to miss execution paths.

As for extensibility, imagine you are writing a framework, where users are supposed to be able to inject their own behavior. For example, you want to create some kind of tax calculations framework, and want to support tax systems of different countries. Instead of implementing all of them, you just want to give framework users a chance to provide an implementation of how to calculate some particular taxes.

Here is the strategy pattern:

  • You define an interface, e.g. TaxCalculation, and your framework accepts instances of this type to calculate taxes
  • A user of the framework creates a class which implements this interface and passes it to your framework, thus providing a way to perform some part of calculations

You cannot do the same with if/else, because that would require changing the code of the framework, in which case it would not be a framework anymore. Since frameworks are often distributed in compiled form, this may be the only option.

Still, even if you just write some regular code, Strategy is beneficial because it makes your intents clearer. It says "this logic is pluggable and conditional", i.e. there can be multiple implementations which may vary depending on user actions, configuration, or even platform.

Using the Strategy pattern may improve readability because, while a class which implements some particular strategy typically should have a descriptive name, e.g. USAIncomeTaxCalculator, if/else blocks are "nameless", in best cases just commented, and comments can lie. Also, fr my personal taste, just having more that 3 if/else blocks in a row is not readable, and it gets pretty bad with nested blocks.

The Open/Closed principle is also very relevant, because, as I described in the example above, Strategy allows you to extend a logic in some parts of your code ("open for extension") without rewriting those parts ("closed for modification").

其他提示

Why is it beneficial to use the strategy pattern if you can just write your code in if/then cases?

Sometimes you should just use if/then. It is simple code that is easy to read.

The two key problems with simple if/then code is that it can violate the open closed principle. If you ever have to go in and add or change a condition, you're modifying this code. If you expect to have more conditions, just adding a new strategy is simpler/cleaner/less-likely-to-break-stuff.

The other problem is coupling. By using if/then, all of the implementations are tied to that implementation, making them harder to change in the future. By using the strategy, the only coupling is to the interface of the strategy.

Strategy is useful when the if/then conditions are based on types, as explained in http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

Type-checking conditionals usually don't have a high cyclomatic complexity, so I wouldn't say that Strategy improves things necessarily there.

The main reason for Strategy is explained in the GoF book p.316 that introduced the pattern:

Use the Strategy pattern when

...

  • a class defines many behaviors, and these appear as multiple conditional statements in its operations. Instead of many conditionals, move related conditional branches into their own Strategy class.

As mentioned in other answers, if applied appropriately the Strategy pattern allows adding new extensions (concrete strategies) without necessarily requiring modifications of the rest of the code. This is the so-called Open-Closed principle or Protected Variations principle. Of course, you still have to code the new concrete strategy, and the client code has to recognize the strategies as plug-ins (this is not trivial).

With if/then conditionals, it's necessary to change the code of the class containing the conditional logic. As mentioned in other answers, sometimes this is OK when you don't want to add the complexity to support adding new functionality (plug-ins) without re-compiling.

[...] if you can just write your code in if/then cases?

That is exactly the greatest benefit of strategic pattern. Not having conditions.

You want your classes/methods/functions to be as simple and short as possible. Short code is very easy to test and very easy to read.

Conditions (if/elseif/else) make your classes/methods/functions long, because usually the code where one decision evaluates to true is different from the part where the decision evaluates to false.


Another great benefit of the strategy pattern is, it is reusable throughout your whole project.

When using the strategy design pattern, you are very likely to have some kind of an IoC container, from which you are obtaining the desired implementation of an interface, perhaps by an getById(int id) method, where the id could be an enumerator member.

This means, the creation of the implementation is only in one place of your code.

If you wish to add more implementations, you add the new implementation to the getById method and this change is reflected everywhere in the code where you call it.

With if/elseif/else this is impossible to do. By adding a new implementation, you have to add a new elseif block and do it everywhere where the implementations were used, or you might end up with a code which is invalid, because you forgot to add the implementation to its structure.


Also, what does it mean for the algorithm to change at runtime?

In my example, the id could be a variable which is populated based on a user input. If the user clicks on a button A, then id = 2, if he clicks on a button B, then id = 8.

Because of the different id value, a different implementation of an interface is obtained from the IoC container and the code performs different operations.

Why is it beneficial to use the strategy pattern if you can just write your code in if/then cases?

The Strategy Pattern lets you separate your algorithms (the details) from you business logic (high level policy). These two things are not only confusing to read when mixed, but also have very different reasons to change.

There is also a major teamwork scalability factor here. Imagine a large programming team where many people are working on this accounting package. If the tax algorithms are all in the TaxPayer class or module, then merge conflicts become likely. Merge conflicts are time-consuming and error-prone to resolve. This time drain saps productivity from the team and the errors introduced by bad merges damage credibility with customers.

Also, what does it mean for the algorithm to change at runtime?

An algorithm that changes at runtime is one whose behavior is determined by configuration or context. An if/then approach in place does not effectively enable this as it involves reloading existing actively used classes. With the Strategy Pattern the strategy objects implementing each algorithm could be constructed on use. As a result changes to these algorithms (bug fixes or enhancements) could be made and reloaded at runtime. This approach could be used to enable continuous availability and zero-downtime releases.

There is nothing wrong with if/else per se. In many cases if/else is the simplest and most readable way of expressing logic. So the approach you describe is perfectly valid in many cases. (It is also perfectly testable, so that is not an issue.)

But there are some particular cases where a strategy pattern may improve the maintainability of the overall code. For example:

  • If the particular tax calculation algorithms may change independently of each other and of the core logic. In this case it would be nice to have them separated into distinct classes, since changes will be localized.
  • If new algorithms may be added in the future, without the core logic changing.
  • If the cause of the difference between the two algorithms also affect other parts of the code. Say you select between the two algorithms based on the income bracket of the tax payer. If this income bracket also cause you select different branches other places in the code, then it is cleaner to instantiate a strategy corresponding to the income bracket once, and call when needed, rather than have multiple if/else-branches scattered over the code.

For the strategy pattern to make sense, the interface between the core logic and the tax calculation algorithms should be more stable than the individual components. If it is just as likely that a requirement change will cause the interface to change, then the strategy pattern might actually be a liability.

It all comes down to if the "tax calculation algorithms" can be cleanly separated from the core logic which invokes it. A strategy pattern have some overhead compared to an if/else, so you will have to decide on a case by case basis if the investment is worth it.

许可以下: CC-BY-SA归因
scroll top