This week's book giveaway is in the OCPJP forum. We're giving away four copies of OCA/OCP Java SE 7 Programmer I & II Study Guide and have Kathy Sierra & Bert Bates on-line! See this thread for details.
I read a few places where people say if you have an if-else construct or a switch statement you can replace it with the strategy pattern. And that the strategy pattern is the correct way to handle if-else chains.
I do have such a chain, but I do not see how the strategy pattern will help me. It seems to simply shift the if-elseing up the chain a bit such that instead of if-elseing within a certain class you start if-elseing at the creation of that class. Ultimately it seems like the if-else only moves but is not destroyed!?
The trick is to move the code that used to be inside each choice in the if-else block into its own class. Strategy classes can wind up being quite short but they can also be as complex as anything else. For example, say we work with a number of appliances:
If I put "toaster stuff" and "blender stuff" and "refrigerator stuff" into three classes I can do something like:
The if-else structure is gone, the code for toaster, blender and refrigerator is separated into three little classes that each do exactly one thing well. This is all good.
Further, I can add new products without touching this method. I can add a mapping from product type to strategy class in configuration and add them without touching the factory, either. Every class I don't modify is one I won't break.
And there's more! Because this routine and the factory don't directly reference RefrigeratorStrategy, there is no compile time dependency. The Stove team can introduce a whole new Stove component to the system without asking the Core team for a new jar.
Finally, if the if-else chain were long enough, the strategy stuff might give us some performance gain. The time to get a strategy from the factory using configuration is about constant while the time to go through the if-else logic grows with every new product.
I'm a strategy fan. Can you tell?
A good question is never answered. It is not a bolt to be tightened into place but a seed to be planted and to bear more seed toward the hope of greening the landscape of the idea. John Ciardi
I'm a Strategy fan, too. On the other hand, not every if-else-chain or switch statement *should* be replaced by Strategy - that would simply be overkill.
I see mainly three motivations for introducing a Strategy in this case:
- The switch is duplicated at other places - whenever I introduce another case, I have to remember to introduce it at the other places, too. (Single Choice Principle)
- The cases that need to be handled change at a different rate than the rest of the class - most often it's less stable. (Single Responsibility Principle)
- Introducing the Strategy considerably simplifies unittesting.
Can you show us your code?
The soul is dyed the color of its thoughts. Think only on those things that are in line with your principles and can bear the light of day. The content of your character is your choice. Day by day, what you do is who you become. Your integrity is your destiny - it is the light that guides your way. - Heraclitus
strategiesByProductTypeMap is a HashMap which in the simplest case is populated hardcoded inside the factory. In other cases, the strategies might be registered from the outside (in which case it's more of a Registry than a Factory) - some kind of plugin mechanism.
Joined: Jan 29, 2003
My factories usually have one of a couple internal strategies of their own.
Either way, it's neat to load the map from configuration. The factory might have methods like:
I like an "application assembler" to read configuration and pump the strategy info into the factory. This makes it easy to plug in special strategies for testing. I got the "assembler" name from Robert Martin's paper on Inversion of Control tho I was already doing it.
But this is just putting the if-else inside of the Map. And then the map will probably be static somewhere.
I checked my program and this chaining is occuring in my factories. Its sorta like so;
1st factory type: pass model into factory factory does if-else chain and creates view based on model type.
2nd factory type: pass model into factory factory does if-else chain and creates a live db connected model based on the model passed in. Sort of like a prototype.
So I guess I can creata a set of creation strategies and put them in a map, then when a particular model is passed in, get the strategy out of the map and execute it?
I didn't realize map was such an intimate part of the strategy pattern [ October 10, 2005: Message edited by: Mr. C Lamont Gilbert ]
Joined: Jan 29, 2003
By using hashes or other advanced lookups maps are a big improvement over regular if-then-else. And even if there was no advantage there, the control over dependencies and the stability of the map lookup over an ever changing if-then-else block often make strategy worth while.
And strategy doesn't have to be that dynamic. I get stuck repeating the way I use them most often, but there are many other choices. You can have strategies that change based on any criteria. Imagine a timer that goes off at 7:00 PM and says
someService.setStrategy( new EveningStrategy() );
with no factory at all. Note that the service might execute the current strategy millions of times with zero if-else tests or map lookups.
The RoboCode robot game has autonomous killer tanks; some of the tank implementations shift strategies based on how near opponents are or how many are left. They might have a short set of if-else choices to set an aiming strategy only when conditions change rather than every time they want to shoot. Again, one strategy shift to any number of passes through the strategy.
Command pattern is very close in structure. If the client passes the strategy into the service it's more of a command. The service might execute the strategy on another machine or shifted later in time:
someService.doOperation( new EveningCommand( theData ) );
It's all good clean fun, no? [ October 10, 2005: Message edited by: Stan James ]
Joined: Jul 11, 2001
Originally posted by Mr. C Lamont Gilbert: But this is just putting the if-else inside of the Map.
If the Map is hardcoded, yes. It can be filled in many of ways, though. And even if it is hardcoded, you still have different kinds of compile time dependencies in your code.
And then the map will probably be static somewhere.
Not so probable when I have any influence on the design...
I didn't realize map was such an intimate part of the strategy pattern
It isn't. It's just one possible way of deciding which strategy to use.
Another way that is quite common is that some code is called from different places, and every such place has hardcoded which Strategy to use. Sometimes they are even implemented as anonymous inner classes.
Since I last posted I implemented the technique with the map ignoring my doubts. It kind of turned into the command mentioned above since my strategy requires data to do its thing, and the data type varies with the strategy.
It has turned out just as you two have stated and that I do in fact not have any static map, and also in some places I am sorta invoking a strategy that is predetermined. its still dynamic to me, but the JVM can tell its really not.
My concern at this point is that I have to load all the strategies when the 'strategy factory' is created; then i stuff them into the map. If I try lazy loading I end up with an if-else.
All in all, its nice. Im losing some type safety, but I seem to be gaining in extensibility.