When and why should the Strategy Pattern be used?
The problem with toy examples such as this is that it is often easy to miss the point. In this case, the code could indeed be just implemented as you have shown. In a strategy pattern, the main value is in being able to switch out different implementations for different situations.
The example you have is only illustrating the objects in the pattern and the interactions between them. Imagine instead that you had a component that renders graphs for a website depending on whether it was a desktop or a smartphone on the other end you would have some code that would detect the type of browser the create and set the strategy on another component that could use the strategy object in some complex code that would not need to be duplicated and would do the work in both situations leaving the details of the actual drawing of the graph to the appropriate strategy object:
interface GraphStrategy {
Image renderGraph(Data graphData);
}
class BigGraphStrategy implements GraphStrategy {
...
}
class SmallGraphStrategy implements GraphStrategy {
...
}
Then in some other code:
GraphStrategy graphStrategy;
if (phoneBrowser == true) {
graphStrategy = new SmallGraphStrategy();
} else {
graphStrategy = new BigGraphStrategy();
}
The rest of your application code can then just use graphStrategy.renderGraph()
without having to know whether full or small image rendering is being performed.
Yeah, the example is lame, but the concept of delegates with different implementation strategies is a basic, very old, design pattern that I've used in lots and lots of apps.
I think your problem here is a common one, though, almost every design pattern example I ever see is incredibly contrived and always leads me to ask questions just like yours - they always seem useless when presented this way.
The code snippet you're quoting is a bit deceptive in that it's (slightly) out of context. What you're writing below in your example is also the strategy pattern - you've just rewritten the above example a bit more concisely.
The main point in the example is that the specifics of the mathematical operation are abstracted away from the caller. This way the caller can work with any binary operator by creating a new ConcreteStrategy, e.g.
int mod = new ConcreteStrategy(){
public int execute(int a, int b){ return a %b; }
}.execute(3,4);
Areas that come to mind:
- A resource allocator. In manual resource management, this might be minimizing the time it takes for the resource to allocate, or minimizing fragmentation. Each strategy here has an "Allocate" method that has the same interface, with the user making a decision about which strategy to use based on what they are trying to optimize.
- A method for connecting and sending network data. Maybe in some cases you would prefer to connect and send UDP datagrams, maybe in other situatinos where performance was less of a factor you would send using TCP/IP.
- Data formatting/serialization strategy. Allow the code to decide whether an object should be serialized with Json or with Xml. Maybe one for machines, and the other for human-readable situations. Both strategies have a "Serialize" method which takes an object. Each serializes differently.
The theme is that the decision on whether to do something one way or another way is dependent on situational factors, and you or your code would choose the correct strategy based on the situation.
Now why would this be more useful than something like:
void DoIt()
{
if (... situation1...)
{
DoA()
}
else
{
DoB();
}
}
The reason is sometimes you want to just make the decision once and forget about it. The other important theme to strategy pattern is that you decouple the decision about which strategy to use from the code that needs to execute the strategy.
DoItStrategy MakeDoItStrategy()
{
if (... situation1...)
{
return new DoItStrategyA();
}
else
{
return new DoItStrategyB();
}
}
In the last example you can just just store the strategy, passing it as just another object that implements the strategy interface. For those executing the strategy, they simply have a way to perform an action. They don't know what the inner workings are under the hood, only that the interface will be satisfied. The users of the strategy shouldn't need to know why we made a decision. They just need to do an action. We make a decision once and passed the strategy to the classes that use the strategy.
For example, consider the case where we make a program-wide decision, based on a given network configuration, to connect and send data to a remote hosts with UDP. Instead of each user of the network interface needing to know the logic to make the decision (the "DoIt" function above), we can create the UDP strategy upfront and pass it to everyone who needs to send network data. This strategy then implements a simple interface with the same end result - data gets from A to B.