Last Updated on September 24, 2023 by KnownSense
The Strategy pattern, also known as Policy is a design pattern that allows you to define a range of methods for achieving a specific task and place each method in its own class. These classes, known as strategies, are interchangeable, providing flexibility in algorithm selection without altering the core code.
Imagine you’re developing a navigation app for travelers. Initially, it could only plan routes for driving, but user demand led to additional features like walking and public transport routes. However, each new route type added complexity, making the main codebase bloated and hard to maintain. Changes to one algorithm impacted the entire system, causing merge conflicts and inefficiency in teamwork.
The Strategy pattern addresses this by separating specific tasks into strategy classes. The context class, responsible for the task, stores a reference to a chosen strategy. The context delegates work to the selected strategy, allowing strategies to be switched easily. This decouples the context from concrete strategies, enabling the addition or modification of algorithms without altering existing code. In the navigation app, each routing algorithm becomes a separate class with a common method, providing a way to switch between routing strategies while maintaining code integrity.
Benefits
- Flexibility and Extensibility: It allows you to define a family of interchangeable algorithms or behaviors, making it easy to add, modify, or extend strategies without altering the context class. This flexibility supports evolving requirements and new functionality.
- Improved Code Organization: Strategies are encapsulated in separate classes, promoting clean and modular code. This separation of concerns enhances code organization and readability.
- Reduced Code Duplication: By extracting common behaviors into strategies, you can avoid code duplication across different parts of your application. This leads to more efficient maintenance and reduces the risk of errors.
- Enhanced Testability: Individual strategies can be tested independently, simplifying unit testing. This results in more effective and targeted testing, which can lead to higher code quality.
- Decoupling of Components: The Strategy Pattern decouples the context class from concrete strategies. This reduces tight coupling and dependencies, making it easier to manage and understand the relationships between classes.
- Runtime Algorithm Selection: You can switch between strategies at runtime, allowing you to adapt to changing conditions or user preferences without restarting the application.
- Ease of Integration: Strategies can be developed and tested independently, making it easier to integrate third-party or legacy code into your application by creating compatible strategy classes.
- Clear Separation of Concerns: The pattern promotes a clear separation between the core logic of the context class and the specific behaviors encapsulated in strategies. This separation makes the codebase more maintainable and easier to comprehend.
- Support for Complex Decision-Making: It is well-suited for scenarios where complex decision-making processes or algorithms need to be implemented. Each strategy can represent a different approach to solving a problem.
- Consistency and Standardization: By using a common interface for strategies, you can ensure a consistent approach to solving related problems throughout your application.
UML of Strategy Pattern
Implementation
Here’s a Java implementation of the navigation app using Strategy Pattern.
Step1: Create RouteStrategy which is the strategy interface that defines the common method buildRoute.
interface RouteStrategy {
List<String> buildRoute(String origin, String destination);
}
Step2: Create Three concrete strategy classes (CarRouteStrategy, WalkRouteStrategy, and PublicTransportRouteStrategy) implement the RouteStrategy interface and provide specific route calculation logic.
class CarRouteStrategy implements RouteStrategy {
@Override
public List<String> buildRoute(String origin, String destination) {
// Implement car route calculation logic
List<String> route = new ArrayList<>();
route.add("Start at " + origin);
route.add("Drive to " + destination);
return route;
}
}
class WalkRouteStrategy implements RouteStrategy {
@Override
public List<String> buildRoute(String origin, String destination) {
// Implement walking route calculation logic
List<String> route = new ArrayList<>();
route.add("Start at " + origin);
route.add("Walk to " + destination);
return route;
}
}
class PublicTransportRouteStrategy implements RouteStrategy {
@Override
public List<String> buildRoute(String origin, String destination) {
// Implement public transport route calculation logic
List<String> route = new ArrayList<>();
route.add("Start at " + origin);
route.add("Take a bus or train to " + destination);
return route;
}
}
Step3: Create the Navigator class which is the context class, responsible for setting the route strategy and planning routes using the chosen strategy.
class Navigator {
private RouteStrategy routeStrategy;
public void setRouteStrategy(RouteStrategy strategy) {
this.routeStrategy = strategy;
}
public List<String> planRoute(String origin, String destination) {
return routeStrategy.buildRoute(origin, destination);
}
}
Step4: In the main method, the navigator is configured with different route strategies, and routes are planned accordingly for various scenarios.
public class NavigationApp {
public static void main(String[] args) {
Navigator navigator = new Navigator();
navigator.setRouteStrategy(new CarRouteStrategy());
List<String> carRoute = navigator.planRoute("Home", "Work");
System.out.println("Car Route:");
carRoute.forEach(System.out::println);
navigator.setRouteStrategy(new WalkRouteStrategy());
List<String> walkRoute = navigator.planRoute("Hotel", "Park");
System.out.println("\nWalking Route:");
walkRoute.forEach(System.out::println);
navigator.setRouteStrategy(new PublicTransportRouteStrategy());
List<String> publicTransportRoute = navigator.planRoute("Airport", "Museum");
System.out.println("\nPublic Transport Route:");
publicTransportRoute.forEach(System.out::println);
}
}
OUTPUT:
Car Route:
Start at Home
Drive to Work
Walking Route:
Start at Hotel
Walk to Park
Public Transport Route:
Start at Airport
Take a bus or train to Museum
Conclusion
Overall, the Strategy Pattern enhances software design by providing a structured way to manage and interchange algorithms or behaviors, leading to more maintainable, modular, and extensible code.