The Strategy Pattern
I will get right to the point about this pattern. Everyone at some point in their code has a switch statement, that depending on the state of an object, you do some crap and it just looks like this:
1 2 3 4 5 | switch(object.getType()){ case Type1:amount=something;break; case Type2:amount=somethingelse;break; case Type3:etcetcetc; } |
Not only is this about as ugly as it gets, it makes it utterly useless when it comes to extensibility and have thrown out any hope of it being reusable. This is an example of tightly coupling functionality that varies to the object. Things like this are difficult to manage over time and every time you think of another case, you get to let this beast of a switch statement grow and grow and grow. So how do we avoid something like this?
This is where the Strategy Pattern comes in. One of the main strengths is it allows you to encapsulate the behavior of an object rather than placing that behavior into the object. This would allow you to not only easily switch out behaviors with ease, but also allow you to switch them out at runtime.
In our switch statement, we were concerned about the amount. Depending on the type, we are calculating the type differently. What we want to do is rather than implementing into our class, we want to just say ‘whatever you are, calculate your amount.’
First thing that we must do is setup an abstract class for our object. What we want to put in here are the behaviors of the object that will change.
1 2 3 4 5 6 7 8 9 10 | public abstract class AmountObject { public AmountObject (){} public AmountStrategy amountStrategy; public void calculateAmount(){ amountStrategy.getAmount(); } public AmountStrategy setAmountStrategy(AmountStrategy as){ amountStrategy = as; } } |
As you can see, its a simple class, all it has is an AmountStrategy object and something that calls getAmount() on it. For this example, lets just say all it does is output the amount. In a more real world example (which i would like to post for you later), we could pass in whatever parameters into the behavior into the function to do the calculations. Lets look how the behaviors will work. First we are going to have an interface for each different type of behavior to implement.
1 2 3 | public interface AmountBehavior { public void getAmount(); } |
Thats it. We have an interface with just its method. Now the implementation of the behavior.
1 2 3 4 5 6 | public class AmountStrategyA implements AmountBehavior{ public void getAmount() { //do some calculations. Usually you would pass in parameters for this System.out.println("This is Strategy A"); } } |
And for the sake of example, lets implement another one.
1 2 3 4 5 6 | public class AmountStrategyB implements AmountBehavior{ public void getAmount() { //do some calculations. Usually you would pass in parameters for this System.out.println("This is Strategy B"); } } |
Now, when we extend our abstract class, we will be able to assign either one of these strategies. As I said earlier, we have encapsulated the behavior of our object rather than coding it straight into our implementation. So lets see how it will work out for us.
1 2 3 4 5 6 | public class AmountStrategyImpl extends AmountStrategy{ public AmountStrategyImpl(){ amountBehavior = new AmountStrategyA (); //whatever other fields that need to be here. } } |
So now we have our implementation with AmountStrategyA. Once again, its pretty straight forward. We have extended our abstract class that has default functionality and fields already there. From our abstract class, we assign the amountBehavior to it. Finally, lets see what it looks like in action.
1 2 3 4 5 6 | public class AmountStrategyExample extends AmountObject { public static void main(String[] args) { AmountStrategyImpl amountStrategy = new AmountStrategyImpl (); amountStrategy.getAmount(); } } |
If you were to compile all these classes and run them, it would output “This is Strategy A”. Now, what we have done is totally isolate the behavior of our object into separate classes and allowed us to assign them to the object depending on the requirements. There is one function that I did not explain in our abstract method though, which is the setAmountStrategy function. What this allows us to do is reassign the strategy AT runtime. So lets say for whatever reason you wanted to change the strategy on how you calculate your amount (IE: You make more money and now your taxes are calculated differently). Add this line at the end of the implementations:
1 2 | amountStrategy.setStrategy(new AmountStrategyB()); amountStrategy.getAmount(); |
Now it will output “This is Strategy B”. Pretty cool huh?
If you were to google Strategy Pattern, one phrase that you will see repeated is “Program for an interface, not for an implementation”, which is exactly what has happened here. We have taken the behavior of the object and placed it in an abstract class and in an interface. We then took the behavior AWAY from the implementation. The implementation should never care how the amount is calculated. All it wants is the amount. The main thing to keep in mind when coding these is think about your object and ask ‘what behavior is static and what behavior is dynamic?’ Take what is dynamic and encapsulate it and take it away from your main object. Then in the implementation, assign the behavior.
Hope this article was helpful. Also if anyone wants to through a heads up to me a good plugin to format the code. I have tried 3 and none seem to work properly (one deleted half of this post grrr). Let me know and I hope you have enjoyed the article!
This is exactly what I stumbled upon a few days ago. Err, what I whish I had found. But really I found a couple of if and switch statements that crumple up lots of functionality into a single method…
I reached this post from a link on a new one, but it seems that there is some problems with naming. The AmountStrategy class is not implemented anywhere (maybe it has been renamed in AmountBehaviour).
Thanks for this great example of the Strategy pattern, this helped me to understand this pattern much better than any other reference I’ve found online so far.
I just wanted to point out a few minor mistakes in the code, which had me a bit confused at first:
* In the AmountObject abstract class, setAmountStrategy should return void.
* The AmountBehavior interface should be named AmountStrategy.
* The AmountStrategyImpl class should extend AmountObject (not AmountStrategy).
* The AmountStrategyExample class should not extend anything.
* In the AmountStrategyExample class, the amountStrategy.getAmount(); line should actually say amountStrategy.calculateAmount();
Amazing post, you’ve helped me understand the strategy!