Software Design Patterns

Strategy

Strategy Pattern

Strategy(315) Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

What does this mean?

This means that we want to build classes for behaviors. An object will be built to represent an algorithm (or a set of algorithms that work together).

What does this accomplish?

We want to be able to change algorithms as needed without breaking our code that uses these algoriths. We also don't want to have change code in many places just because we change the algorithm being used. This will mean your code has more classes. Don't allow yourself to believe the false assumption that more classes is bad. The design principles dictate that our code should have more finely grained classes:

  • separate what varies
  • Loose coupling, High cohesion

More classes makes your code more modular and easier to change without causing side effects when it is done correctly.

The Example

You can find many simple examples of the Strategy Pattern, so I have provided more complex example meant to show you how Strategy can be used in a real-world application. This is still a somewhat academic example but should give you some real ideas about how you can use Strategy in your own code.

This example is of an shopping cart order processing scenario. The tasks that must be completed in order to complete an order include:

    Tasks
  • Calculate state sales tax.
  • Perform a fraud risk check.
  • Authorize the credit card.
  • Encrypt sensitive data for storage in database.

Example without Strategy Pattern

In the first example, without Strategy, I have all the above algorithms buried in the OrderProcesser class using conditional logic to choose which algorithm to use for the above tasks, and the code for performing each task also in that class. So we have a really large OrderProcesser class, but all the tasks related to processing an order are right there in one place.

NoStrategyProcessOrder.gif


Example with Strategy Pattern

The second example utilizes a Strategy Pattern to put sets of algorithms into groups of classes. Each type of algorith (tax, encryption, authorization, fraud check) will inherit from a common interface. Our OrderProcessor class only depends on the interfaces and knows nothing of the derived types that are actually used.

This accomplishes high cohesion because we have code for a specific tasks separated into separate classes, instead of the OrderProcesser class doing everything. In other words, the classes are responsible for one very specific job.

This accomplishes loose coupling because the OrderProcesser class is not dependent on specific classes, but rather a very general inteface for that family of classes. We can switch classes (even at run-time) and know that our OrderProcesser class will still run correctly. We can also add new classes to each family of classes and as long as they implement the interface, they can be used safely.

You will notice a class called ConfigData which implements the Singleton pattern. This singleton is called by our code to determine which specific class should be used. I have hardcoded which specific classes are to be used but it is possible to use conditional logic and refer to config files so that you can let your code determine at run-time the specifi class to use.

ProcOrder_WithStrat.gif

If you are looking at the above thinking that "things just got a lot more complicated", don't worry. It looks more complicated because there are more classes, but the code in the OrderProcesser got much more simplified and we have merely pulled the complex code out and separated. We now have an application that is very modular and resistent to breaking changes when we must make changes.

Let's say that you change credit card processors. You no longer have to dig through your code in the OrderProcesser class and change things. All you need to do is implement a new class that inherits from CreditCardProcesser and set your ConfigData class to use the new processer.

Let's imagine that you build your ConfigDataclass to read a config file and use conditional logic to return the correct types of objects for use. Let's assume that you have classes for the following:

  • 15 Credit card processers
  • 5 Fraud prevention services
  • 50 states (for state sales tax).
  • 4 encryption/decryption algorithms

By setting values in a config file, your software user can setup 15,000 possible configurations without requiring any changes to your compiled code.


Code Samples

The code samples are in C# and are not fully implemented. I have implemented only enough for the code to successfully compile so the implementation of the functions are mostly empty. This is so you can focus on the structure not the unrelated details.  It should be enough that you can follow the logic of how and why you would use the Strategy Pattern.

Also note that I have comments in the code about how we removed 'messy conditional logic', but understand that there are cases when you will want to use conditional logic (like in the ConfigData singleton class), to make a more dynamic application.

The purpose of the Strategy Pattern is to separate code for algorithms into separate classes, NOT to get rid of conditional logic. I've read some sites where people seem to be real concerned about using the Strategy Pattern to get rid of conditional logic and think they have failed if they haven't done so, and I believe they have gotten lost.  Yes you should remove the conditional logic from the clients (OrderProcesser class in this case), and move it into a central location (ConfigData in this case). This will keep clients from being tightly coupled to the behavior classes.

1. C# code without Strategy Pattern.
2. C# code with Strategy Pattern.

0 Comments