Behavioral Design Patterns
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.
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.
Context will call to execute the algorithm (e.g., calculateRoute()).Strategy interface, providing the logic for one specific algorithm (e.g., DrivingStrategy, WalkingStrategy).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 the Strategy interface.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");
}
}
Benefits:
RoutePlanner from the low-level, complex details of the individual routing algorithms.if-else or switch statement for selecting an algorithm.Drawbacks:
Concrete Strategy to the Context is a form of Dependency Injection. This decouples the context from the creation of its own dependencies.