- Shift Elevate
- Posts
- Strategy Pattern: Encapsulate Algorithms and Make Them Interchangeable
Strategy Pattern: Encapsulate Algorithms and Make Them Interchangeable
The Pain of Hardcoded Algorithm Selection
Picture this: You're building a navigation system for a GPS application that needs to calculate routes using different strategies: fastest route using highways, shortest route by distance, and eco-friendly route for fuel efficiency. Your initial approach seems logical: use conditional logic to select the appropriate calculation method.
Then business requirements evolve. The product team wants to add bicycle routes, avoid tolls, prefer scenic roads, and incorporate real-time traffic patterns. Your route calculation method becomes a massive switch statement that violates the Open Closed Principle and breaks every time you add a new routing strategy.
Suddenly, you're dealing with code like this:
public Route calculateRoute(Location start, Location end, String routeType) {
if (routeType.equals("FASTEST")) {
return calculateFastestRoute(start, end);
} else if (routeType.equals("SHORTEST")) {
return calculateShortestRoute(start, end);
} else if (routeType.equals("ECO")) {
return calculateEcoRoute(start, end);
} else if (routeType.equals("SCENIC")) {
// Scenic route logic
} else if (routeType.equals("BICYCLE")) {
// Bicycle-friendly route logic
}
// ... endless conditions
}Sound familiar? The Strategy pattern solves this challenge by encapsulating algorithms in separate strategy objects, making them interchangeable at runtime.
Understanding the Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it, enabling runtime algorithm selection.
Think of it like choosing different navigation modes on Google Maps: whether you select the fastest route, avoid highways, or optimize for fuel efficiency, the navigation system remains the same. Each routing method is a different strategy that implements the same interface, but with different internal logic.
This pattern promotes Algorithm Flexibility, Open Closed Principle, and Single Responsibility while enabling dynamic behaviour selection at runtime.

Strategy Pattern Components
Core Components
Strategy Interface: Defines the common interface for all concrete strategies
Supporting Classes: Helper classes used by strategies (Location, Route)
Concrete Strategies: Implement specific algorithms using the strategy interface
Context Class: Uses a strategy to execute algorithms and can switch strategies at runtime
Complete Java Implementation
Let's build a navigation route planning system that demonstrates the Strategy pattern's power in managing different routing algorithms.
Strategy Interface
public interface RouteStrategy {
Route calculateRoute(Location start, Location destination);
String getStrategyName();
}Supporting Classes
public class Location {
private String name;
private double latitude;
private double longitude;
public Location(String name, double latitude, double longitude) {
this.name = name;
this.latitude = latitude;
this.longitude = longitude;
}
public String getName() { return name; }
public double getLatitude() { return latitude; }
public double getLongitude() { return longitude; }
@Override
public String toString() {
return name;
}
}
public class Route {
private Location start;
private Location destination;
private double distance;
private int estimatedTime;
private double fuelCost;
private String description;
public Route(Location start, Location destination, double distance,
int estimatedTime, double fuelCost, String description) {
this.start = start;
this.destination = destination;
this.distance = distance;
this.estimatedTime = estimatedTime;
this.fuelCost = fuelCost;
this.description = description;
}
public Location getStart() { return start; }
public Location getDestination() { return destination; }
public double getDistance() { return distance; }
public int getEstimatedTime() { return estimatedTime; }
public double getFuelCost() { return fuelCost; }
public String getDescription() { return description; }
@Override
public String toString() {
return String.format("Route: %s → %s\n" +
" %s\n" +
" Distance: %.1f km\n" +
" Time: %d minutes\n" +
" Fuel Cost: $%.2f",
start.getName(), destination.getName(),
description, distance, estimatedTime, fuelCost);
}
}Concrete Strategies
public class FastestRouteStrategy implements RouteStrategy {
@Override
public Route calculateRoute(Location start, Location destination) {
double distance = calculateDistance(start, destination) * 1.15;
int estimatedTime = 32;
double fuelCost = 6.50;
String description = "Via Highway 101 (fastest route with minimal traffic)";
return new Route(start, destination, distance, estimatedTime, fuelCost, description);
}
@Override
public String getStrategyName() {
return "Fastest Route (via highways)";
}
private double calculateDistance(Location start, Location destination) {
double latDiff = destination.getLatitude() - start.getLatitude();
double lonDiff = destination.getLongitude() - start.getLongitude();
return Math.sqrt(latDiff * latDiff + lonDiff * lonDiff) * 111;
}
}
public class ShortestRouteStrategy implements RouteStrategy {
@Override
public Route calculateRoute(Location start, Location destination) {
double distance = calculateDistance(start, destination);
int estimatedTime = 55;
double fuelCost = 5.80;
String description = "Via City Center (shortest distance route)";
return new Route(start, destination, distance, estimatedTime, fuelCost, description);
}
@Override
public String getStrategyName() {
return "Shortest Route (via city center)";
}
private double calculateDistance(Location start, Location destination) {
double latDiff = destination.getLatitude() - start.getLatitude();
double lonDiff = destination.getLongitude() - start.getLongitude();
return Math.sqrt(latDiff * latDiff + lonDiff * lonDiff) * 111;
}
}
public class EcoFriendlyRouteStrategy implements RouteStrategy {
@Override
public Route calculateRoute(Location start, Location destination) {
double distance = calculateDistance(start, destination) * 1.05;
int estimatedTime = 42;
double fuelCost = 4.90;
String description = "Via Suburban Roads (optimized for fuel efficiency)";
return new Route(start, destination, distance, estimatedTime, fuelCost, description);
}
@Override
public String getStrategyName() {
return "Eco-Friendly Route (fuel optimized)";
}
private double calculateDistance(Location start, Location destination) {
double latDiff = destination.getLatitude() - start.getLatitude();
double lonDiff = destination.getLongitude() - start.getLongitude();
return Math.sqrt(latDiff * latDiff + lonDiff * lonDiff) * 111;
}
}Context Class
public class NavigationSystem {
private RouteStrategy routeStrategy;
private Location currentLocation;
public NavigationSystem() {
// Default strategy
this.routeStrategy = new FastestRouteStrategy();
}
public void setRouteStrategy(RouteStrategy strategy) {
this.routeStrategy = strategy;
System.out.println("Route strategy changed to: " + strategy.getStrategyName());
}
public void setCurrentLocation(Location location) {
this.currentLocation = location;
}
public Route planRoute(Location destination) {
if (currentLocation == null) {
throw new IllegalStateException("Current location not set");
}
System.out.println("\n=== Planning Route ===");
System.out.println("Strategy: " + routeStrategy.getStrategyName());
Route route = routeStrategy.calculateRoute(currentLocation, destination);
return route;
}
public void displayRouteOptions(Location destination) {
System.out.println("\n=== Available Route Options ===");
System.out.println("From: " + currentLocation.getName());
System.out.println("To: " + destination.getName());
System.out.println();
RouteStrategy[] strategies = {
new FastestRouteStrategy(),
new ShortestRouteStrategy(),
new EcoFriendlyRouteStrategy()
};
for (RouteStrategy strategy : strategies) {
Route route = strategy.calculateRoute(currentLocation, destination);
System.out.println(route);
System.out.println();
}
}
}Client
public class NavigationDemo {
public static void main(String[] args) {
System.out.println("=== GPS Navigation System Demo ===\n");
// Create locations
Location home = new Location("Home", 37.7749, -122.4194);
Location office = new Location("Downtown Office", 37.8044, -122.2712);
// Create navigation system
NavigationSystem navigation = new NavigationSystem();
navigation.setCurrentLocation(home);
// Display all available route options
navigation.displayRouteOptions(office);
System.out.println("=".repeat(60));
// Plan route using fastest strategy (default)
System.out.println("\n1. Using Fastest Route Strategy:");
Route fastestRoute = navigation.planRoute(office);
System.out.println(fastestRoute);
// Change to shortest distance strategy
System.out.println("\n2. Using Shortest Route Strategy:");
navigation.setRouteStrategy(new ShortestRouteStrategy());
Route shortestRoute = navigation.planRoute(office);
System.out.println(shortestRoute);
// Change to eco-friendly strategy
System.out.println("\n3. Using Eco-Friendly Route Strategy:");
navigation.setRouteStrategy(new EcoFriendlyRouteStrategy());
Route ecoRoute = navigation.planRoute(office);
System.out.println(ecoRoute);
}
}Expected Output:
=== GPS Navigation System Demo ===
=== Available Route Options ===
From: Home
To: Downtown Office
Route: Home → Downtown Office
Via Highway 101 (fastest route with minimal traffic)
Distance: 19.3 km
Time: 32 minutes
Fuel Cost: $6.50
Route: Home → Downtown Office
Via City Center (shortest distance route)
Distance: 16.8 km
Time: 55 minutes
Fuel Cost: $5.80
Route: Home → Downtown Office
Via Suburban Roads (optimized for fuel efficiency)
Distance: 17.6 km
Time: 42 minutes
Fuel Cost: $4.90
============================================================
1. Using Fastest Route Strategy:
=== Planning Route ===
Strategy: Fastest Route (via highways)
Route: Home → Downtown Office
Via Highway 101 (fastest route with minimal traffic)
Distance: 19.3 km
Time: 32 minutes
Fuel Cost: $6.50
2. Using Shortest Route Strategy:
Route strategy changed to: Shortest Route (via city center)
=== Planning Route ===
Strategy: Shortest Route (via city center)
Route: Home → Downtown Office
Via City Center (shortest distance route)
Distance: 16.8 km
Time: 55 minutes
Fuel Cost: $5.80
3. Using Eco-Friendly Route Strategy:
Route strategy changed to: Eco-Friendly Route (fuel optimized)
=== Planning Route ===
Strategy: Eco-Friendly Route (fuel optimized)
Route: Home → Downtown Office
Via Suburban Roads (optimized for fuel efficiency)
Distance: 17.6 km
Time: 42 minutes
Fuel Cost: $4.90🚀 Get the Complete Implementation
The full code with advanced strategy selection and dynamic routing is available in our Design Patterns Repository.
# Clone and run the complete demo
git clone https://github.com/shift-elevate/design-patterns.git
cd design-patterns
mvn test -Dtest=StrategyPatternTestReal World Examples
The Strategy pattern is extensively used in real-world applications:
1. Payment Processing Systems
Modern payment gateways use Strategy patterns to handle different payment methods (credit cards, digital wallets, bank transfers), with each method implementing the same payment interface but with different processing logic.
2. Data Compression Libraries
Compression libraries use Strategy patterns to support different compression algorithms (ZIP, GZIP, BZIP2), allowing users to select the appropriate algorithm based on speed vs. compression ratio requirements.
3. Machine Learning Frameworks
ML frameworks use Strategy patterns for different algorithms (linear regression, decision trees, neural networks), enabling easy algorithm swapping and comparison without changing the training infrastructure.
When to Use Strategy Pattern
Understanding when to apply the Strategy pattern is crucial for making the right architectural decisions. Here's when it shines and when alternatives might be better:
✅ Ideal Scenarios:
You have multiple ways to perform a task and want to choose at runtime.
You want to avoid large conditional statements for algorithm selection.
You need to add new algorithms without modifying existing code.
Different algorithms have similar interfaces but different implementations.
❌ Skip It When:
You only have one or two simple algorithms that rarely change.
The algorithms are tightly coupled to the context and can't be easily separated.
Performance overhead of strategy objects is unacceptable.
The algorithm selection logic is simple and stable.
Next Steps: Apply Strategy Pattern in Your Project
Ready to implement the Strategy pattern in your own projects? Here's a structured approach to get you started:
Identify Algorithm Variations: Look for areas where you have multiple ways to accomplish the same task (routing, pricing, sorting, validation).
Define Strategy Interface: Create a common interface that all algorithm variations can implement.
Implement Concrete Strategies: Build separate classes for each algorithm variation.
Create Context Class: Build a class that uses strategies and can switch between them.
Add Strategy Selection Logic: Implement logic to choose appropriate strategies based on conditions.
The Strategy pattern transforms rigid algorithmic code into flexible, extensible systems. By encapsulating algorithms in separate strategy objects, you build systems that can adapt to changing requirements while maintaining clean, testable code.
Found this helpful? Share it with a colleague who's struggling with complex algorithm selection logic. Got questions? We'd love to hear from you at [email protected]