15

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?

  • 2
    Is this homework? It's better to state that up-front if so. – Fuhrmanator Nov 16 '15 at 14:00
  • 2
    @Fuhrmanator no it isnt – Armon Safai Nov 16 '15 at 23:13
20

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").

  • 1
    if/else blocks also lower code readability. As for strategy pattern, Open/Closed principle is worth mentioning IMO. – Maciej Chałapuk Nov 15 '15 at 17:55
  • 1
    Testability is the big reason. (Most) Every branch in your code should be tested. The more ifs you have, the more possible paths exist through your code, the more tests you have to write and more ways for that method to fail. If I may quote the late Yogi Berra: "If you come to a fork in the road take it." This applies brilliantly to unit testing. Furthermore, many if statements means you are likely to repeat the logic for those conditions, further increasing your test load and increasing the risk of bugs cropping up. – Greg Burghardt Nov 15 '15 at 17:57
  • thank you for the answer. So why cant i use separate methods for different algorithms in the same class? – Armon Safai Nov 15 '15 at 20:28
  • You can, but you still would need a bunch of if/else blocks to call them (or inside them, to determine if it should do something or not), so it's not much of a help, except maybe more readable code. And also no extensibility for users of your hypothetical framework then. – scriptin Nov 15 '15 at 20:38
  • 1
    Can you be clearer about why it's easier to test? The example for refactoring a case statement (or if/then) to a polymorphic method (basis for strategy) is pretty easy to test. refactoring.com/catalog/replaceConditionalWithPolymorphism.html If I know all the conditions to test for, I write a test for each. If I have strategies, I have to instantiate and execute one for each. How is the strategy approach easier to test? We're not talking about complex nested ifs when you refactor to strategy. – Fuhrmanator Nov 16 '15 at 14:06
5

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.

  • what is wrong with modifying the code in the if/then code? wouldnt you have to modify code in strategy pattern as well if you decide to change how one of the algorithms functions? – Armon Safai Nov 15 '15 at 20:01
  • @armonsafai - if you modify the strategy, you just need to test the strategy. If you modify all of the algorithms, you need to test all the algorithms. Worse, if you add a new strategy, you just need to test the strategy. If you add a new conditional, you need to test all the conditionals. – Telastyn Nov 15 '15 at 23:33
4

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.

3

[...] 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.

  • thank you for the answer. So why cant i use separate methods for different algorithms in the same class? – Armon Safai Nov 15 '15 at 20:28
  • @ArmonSafai Would separate methods really solve anything? I don't think so. You are moving the problem from one place to another, and the decision which method to call will be made based on a result of a condition. Again, an if/elseif/else state. Same as before, just in a different place. – Andy Nov 15 '15 at 20:53
  • So the if/then cases would be in main right? Wouldnt you have to use if/then cases in main for the strategy pattern as well? – Armon Safai Nov 15 '15 at 21:05
  • 1
    @ArmonSafai No, you would not. You would have a switch on the id variable in the getById method, which would return the specific implementation. Every time you would need an implementation of the interface, you would ask the IoC container to deliver it to you. – Andy Nov 15 '15 at 21:07
  • 1
    @ArmonSafai You could just as well have a method getSortByEnumType(SortEnum type) returning an implementation of a Sort interface, have a method getSortType returning a SortEnum variable and taking a collection as a parameter, and the getSortByEnumType method would again contain a switch on the type parameter returning you the correct sorting algorithm. If you needed to add a new sorting algorithm, you only need to edit the enum and one method. And you are set. – Andy Nov 15 '15 at 21:39
2

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.

1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.