Behavioural design patterns are about how objects work together and who does which job. The Strategy Pattern lets a program pick one way of doing a job while it is running (at runtime), instead of fixing it when the code is written.
Imagine a navigation app. You choose driving, walking or cycling, and the app works out a different route for each. If one class held the route steps for all three modes, it would grow big and be hard to change. It is better to keep each way of finding a route in its own class and pick one when needed.
1. What is the Strategy Pattern?
The Strategy Pattern is a Behavioural design pattern. It puts each way of doing a job (an algorithm, which is just a set of steps) in its own class. All these classes share one interface (a list of methods every strategy must have). This lets the program swap one for another at runtime, depending on the situation (the context).
Formal Definition
The Strategy Pattern lets you choose how a job is done at runtime. Each way of doing the job (a strategy) lives in its own class. All of them share a common interface, so they can be swapped.
It changes the behavior of an object while the program runs, without editing the object's class.
Real-Life Analogy
A ride-hailing app matches a rider with a driver. How it picks the driver changes with the situation, such as how close the drivers are, where the rider is, and how busy it is:
- The Context:
RideMatchingService, the class that receives the request and uses a strategy. - The Strategies: The different ways to match, such as the nearest available driver, surge zones first, or a first-in, first-out (FIFO, first to arrive is served first) airport queue.
- The Interface:
MatchingStrategy, the one shared method signature (name and inputs). The service can use any strategy without knowing which one it has.
2. Understanding the Problem: Monolithic Conditionals
Without the pattern, one method holds every matching rule inside an if-else or switch block. In the Before/ code, matchRider takes a string such as "NEAREST" or "AIRPORT_QUEUE" and checks it branch by branch.
- Violation of Open/Closed Principle (OCP): This principle says code should be open to new features but closed to edits of working code. To add VIP rider matching, you must edit
RideMatchingService. Each edit can break working rules. - Conditional Code Bloat: Every new matching type adds another branch. The method keeps growing and becomes hard to read.
- Difficult to Test or Reuse: You cannot test one matching rule alone. You must call the whole service and pass through its
if-elsechain. - No Separation of Concerns: Each class should do one job. Here the service both receives the request and holds the matching math for every rule.
3. Class Diagram & Structural Breakdown
The pattern removes the if-else chain. The service holds a strategy object (composition, meaning one object holds another) and hands the work to it (delegation).
In the diagram, the service points only to the MatchingStrategy interface. Three classes implement that interface. The service never names a concrete strategy.
| Component | Responsibility |
|---|---|
Context (RideMatchingService) | Holds a reference to a strategy object and passes the work to it. |
Strategy Interface (MatchingStrategy) | Gives every strategy the same method signature. |
Concrete Strategies (NearestDriverStrategy, etc.) | Each one holds the steps of a single algorithm. |
4. How Strategy Solves the Earlier Problems
| Problem in Old Approach | How Strategy Pattern Solves It |
|---|---|
| Violation of Open/Closed Principle | To add a strategy, write a new class that implements the interface. The existing code stays untouched. |
| Messy Conditional Code | Each if-else or switch branch becomes its own class. The service just calls the strategy it holds. This is polymorphism: the same call does different work depending on the object. |
| Difficult to Test or Reuse | Each strategy is a small class you can test on its own. |
| No Separation of Concerns | The context decides when to run the matching. The strategy knows how. |
5. Pros and Cons
Pros
- Supports the Open/Closed Principle (OCP): You can add new strategies without changing existing code.
- Easy to Add New Behaviors: Each behavior lives in its own class, so a new one is just a new class.
- Enables Runtime Behavior Changes: You can swap the strategy object while the program runs by calling
setStrategy(setter injection: passing the object in through a setter method). - Encourages Composition Over Inheritance: With inheritance, behavior is fixed by the class hierarchy when the code is compiled. With composition, the object holds a strategy that it can change at runtime.
Cons
- May Lead to Too Many Small Classes: One class per strategy adds more files and more code.
- Requires Awareness of All Strategies: The client (the code that picks a strategy) must know which strategies exist and how they differ.
- Slight Overhead Due to Interfaces: The extra structure is not worth it for simple, stable logic.
6. Suitable Scenarios for Strategy Pattern
- Multiple Interchangeable Algorithms: Several ways to do one job, such as different sorting algorithms, billing tax rules or payment processors.
- Compliance with Open/Closed Principle (OCP): New variants arrive over time, and the old ones must keep working.
- Elimination of Conditionals: A large
if-elseorswitchblock decides which behavior to run. - Behavior-Specific Unit Testing: You want to test one calculation on its own, without setting up the whole service.