Strategy
Behavioral Design Patterns
The Problem: Hardcoded Algorithm
Imagine you're developing a map application that calculates routes between two points. Initially, it only calculates driving routes. The logic might be hardcoded directly inside a RoutePlanner
class.
public class RoutePlanner {
public void calculateRoute(Point start, Point end) {
System.out.println("Calculating driving route...");
// Complex algorithm considering roads, traffic, speed limits...
}
}
This works until the product team asks for new features: add a walking route and a public transit route. The naive solution is to cram this new logic into the RoutePlanner
using a big if-else if
block.
public class RoutePlanner {
private String travelMode;
public void setTravelMode(String mode) { this.travelMode = mode; }
public void calculateRoute(Point start, Point end) {
if ("driving".equals(travelMode)) {
System.out.println("Calculating driving route...");
// ...
} else if ("walking".equals(travelMode)) {
System.out.println("Calculating walking route...");
// ...
} else if ("transit".equals(travelMode)) {
System.out.println("Calculating public transit route...");
// ...
}
}
}
This class is now a mess. It violates the Single Responsibility Principle by trying to manage multiple complex algorithms. More importantly, it violates the Open/Closed Principle, because adding a new Bicycling
mode requires modifying this central class.
Encapsulating Interchangeable Algorithms
The Strategy is a behavioral design pattern that defines a family of algorithms, encapsulates each one into a separate class, and makes their objects interchangeable. This lets the algorithm vary independently from the clients that use it.
The core idea is to treat different algorithms as swappable "strategy" objects. You extract the algorithms from the main class (the "Context") and put each into its own class, all of which implement a common Strategy
interface. The Context then holds a reference to one of these strategy objects. When it needs to perform the task, the Context simply delegates the work to its current strategy object. The client can change the Context's strategy at any time.
The Participants
- Strategy: The common interface for all the different algorithms. It declares the method that the
Context
will call to execute the algorithm (e.g.,calculateRoute()
). - Concrete Strategy: A class that implements the
Strategy
interface, providing the logic for one specific algorithm (e.g.,DrivingStrategy
,WalkingStrategy
). - Context: The class that is configured with a
Concrete Strategy
and maintains a reference to it. It does not know the details of the algorithm; it only knows how to call it through theStrategy
interface.
Example: A Route Planning Application
Let's refactor our RoutePlanner
to use the Strategy pattern.
// The Strategy interface
public interface IRouteStrategy {
void calculateRoute(String startPoint, String endPoint);
}
// Concrete Strategies
public class DrivingStrategy implements IRouteStrategy {
@Override
public void calculateRoute(String startPoint, String endPoint) {
System.out.println("Calculating driving route from " + startPoint + " to " + endPoint);
}
}
public class WalkingStrategy implements IRouteStrategy {
@Override
public void calculateRoute(String startPoint, String endPoint) {
System.out.println("Calculating walking route from " + startPoint + " to " + endPoint);
}
}
public class TransitStrategy implements IRouteStrategy {
@Override
public void calculateRoute(String startPoint, String endPoint) {
System.out.println("Calculating public transit route from " + startPoint + " to " + endPoint);
}
}
// The Context class
public class RoutePlanner {
private IRouteStrategy strategy;
// The client can set the strategy at any time.
public void setStrategy(IRouteStrategy strategy) {
this.strategy = strategy;
}
public void planRoute(String startPoint, String endPoint) {
// The context delegates the work to its current strategy object.
strategy.calculateRoute(startPoint, endPoint);
}
}
// The Client, which chooses the strategy
public class Client {
public static void main(String[] args) {
RoutePlanner planner = new RoutePlanner();
// User wants to drive
planner.setStrategy(new DrivingStrategy());
planner.planRoute("Home", "Office");
// Now the user wants to walk
planner.setStrategy(new WalkingStrategy());
planner.planRoute("Office", "Lunch Place");
}
}
Interview Focus: Analysis and Trade-offs
Benefits:
- Adherence to OCP: You can introduce new algorithms (new strategy classes) without changing the context class at all. The design is open to extension but closed for modification.
- Separation of Concerns: It cleanly separates the high-level logic of the
RoutePlanner
from the low-level, complex details of the individual routing algorithms. - Eliminates Conditionals: It provides a clean, object-oriented alternative to a large
if-else
orswitch
statement for selecting an algorithm. - Runtime Swapping: The behavior of the context object can be changed dynamically at runtime by providing it with a different strategy object.
Drawbacks:
- Increased Number of Objects: You create a new class for every distinct algorithm, which can add some complexity to your application if you only have a few simple strategies that rarely change.
- Client Awareness: The client often needs to know about the different strategies so it can select the appropriate one and pass it to the context.
How Strategy Relates to Other Concepts
- Strategy vs. State: This is a classic and very important interview question, as the two patterns are structurally identical. The difference is in their intent.
- Strategy is about how an object performs a task. The client is usually responsible for choosing the strategy. It's about having different, swappable algorithms.
- State is about what state an object is in and how it behaves in that state. The context or the state objects themselves are responsible for transitions between states. The client often doesn't control the state changes directly.
- Open/Closed Principle: The Strategy pattern is a textbook example of how to implement OCP. The context is closed, but the system can be extended with new strategies.
- Dependency Injection: The client's act of providing a
Concrete Strategy
to theContext
is a form of Dependency Injection. This decouples the context from the creation of its own dependencies.