Strategy pattern
The Strategy pattern is a behavioral design pattern that defines a family of interchangeable algorithms, encapsulates each algorithm as a separate class implementing a common interface, and allows clients to select and switch between them at runtime without altering the client code. This pattern enables algorithms to vary independently from the clients that use them, promoting flexibility in software design by avoiding hard-coded conditional logic for behavior selection.
Introduced in the 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides—commonly known as the Gang of Four (GoF)—the Strategy pattern addresses common challenges in object-oriented programming where multiple variants of an algorithm need to be supported dynamically.[1] The pattern's core structure involves three main participants: a Context class that maintains a reference to a Strategy object and delegates algorithm execution to it; a Strategy interface or abstract class declaring the algorithm's method; and concrete Strategy classes that implement specific algorithms. This composition allows the Context to remain unchanged while swapping strategies, adhering to the open-closed principle by extending behavior through new strategy classes rather than modifying existing code.[2]
The Strategy pattern is applicable in scenarios involving families of related algorithms, such as sorting methods, payment processing options, or compression techniques, where the choice of algorithm depends on runtime conditions like data size or user preferences. It is particularly useful for eliminating large switch statements or if-else chains that select behaviors, instead providing a more maintainable and extensible alternative. Key advantages include defining a clear hierarchy of interchangeable algorithms, enabling reuse across contexts, and simplifying unit testing by isolating strategies; however, it can introduce complexity through additional classes and may not be ideal if strategies are few or simple enough for direct parameterization.[2] The pattern relates to others like State (which also encapsulates varying behavior but focuses on object state changes) and Flyweight (sharing strategies to optimize memory), and it contrasts with Template Method by allowing full algorithm replacement rather than partial variation.
Definition and Intent
Definition
The Strategy pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one as an object, and makes them interchangeable within the context of a client, allowing the algorithm to vary independently from clients that use it.[3]
It consists of three key components: the Context, which is the client class that uses a Strategy; the Strategy, an interface or abstract class that declares the algorithm to be implemented; and ConcreteStrategy, concrete classes that implement the Strategy interface to provide specific algorithm behaviors.[3]
The pattern was introduced in the 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, commonly known as the Gang of Four.[3]
As a behavioral design pattern, it focuses on the communication between objects, using inheritance and composition to manage algorithms and responsibilities.[3]
Intent and Motivation
The intent of the Strategy pattern is to define a family of algorithms, encapsulate each one within its own class, and make them interchangeable at runtime, allowing the algorithm to vary independently from the clients that use it.[4]
This pattern is motivated by scenarios where a single class must perform different behaviors based on varying conditions, such as selecting among multiple algorithms depending on input data or environmental factors. Without Strategy, such flexibility often results in bloated code filled with conditional statements (e.g., numerous if-else branches to choose between sorting algorithms like quicksort for large datasets or insertion sort for small ones), which violates the single responsibility principle and makes maintenance difficult.[5][6]
By delegating algorithm selection and execution to interchangeable strategy objects, the pattern separates the concerns of behavior choice from the core logic of the context class, reducing complexity and enabling runtime swaps without client-side modifications.[4]
A practical example is a payment processing system that must handle diverse methods like credit card authorization or PayPal integration; embedding each method's logic directly in the processor class would lead to entangled code, whereas treating them as strategies allows seamless addition or substitution of payment options.[5]
Structure
Class Diagram
The class diagram of the Strategy pattern depicts a static structure that supports the encapsulation and interchangeability of algorithms through object composition and polymorphism. As outlined in the foundational work on design patterns, the diagram features a small set of interrelated classes without complex hierarchies.
The primary components include the Context class, which represents the client that uses the algorithm; the Strategy abstract class or interface; and one or more ConcreteStrategy classes. The Context class contains a private reference to a Strategy object, typically named strategy, and includes a public method such as contextInterface() that delegates the core algorithmic work by invoking the Strategy's algorithmInterface() method.[4][7]
The Strategy component declares the abstract algorithmInterface() method, serving as the common contract for all algorithm variants without providing an implementation itself. Each ConcreteStrategy class, such as ConcreteStrategyA or ConcreteStrategyB, implements this method with a specific algorithm, enabling diverse behaviors like sorting via quicksort or mergesort.[4][7]
Key relationships in the diagram consist of a composition association (denoted by a filled diamond) from Context to Strategy, signifying that the Context owns and manages the lifecycle of its Strategy instance; and realization relationships (dashed arrows with hollow triangles) from each ConcreteStrategy to Strategy, indicating implementation of the abstract method. Notably, no inheritance exists between Context and Strategy, preserving their independence.[4][7]
This arrangement facilitates polymorphism, where the Context interacts solely with the Strategy interface, allowing seamless substitution of ConcreteStrategy instances at runtime without modifying the Context code.[7][4]
A frequent variation replaces the Strategy interface with an abstract class to accommodate shared helper methods or default implementations across ConcreteStrategies, such as common preprocessing steps, while still supporting multiple concrete realizations.[4]
Sequence Diagram
The sequence diagram for the Strategy pattern depicts the runtime interactions that enable interchangeable algorithms, focusing on how the Client configures the Context with a specific strategy and how the Context delegates execution without knowledge of the concrete implementation. As defined in the seminal work by Gamma et al., the diagram includes lifelines for the Client (which initiates the process), the Context (which maintains a reference to the Strategy interface), the abstract Strategy, and one or more ConcreteStrategy instances (which provide the actual algorithmic behavior).
The primary flow begins with the Client instantiating a ConcreteStrategy object and passing it to the Context via a setter method, such as setStrategy(). This establishes the polymorphic reference in the Context. Subsequently, the Client sends a request message to the Context, prompting the Context to invoke the execute() or equivalent algorithm method on its Strategy reference. The call resolves dynamically to the ConcreteStrategy's implementation, where the specific algorithm is performed, and the result is returned through the delegation chain back to the Client. This interaction highlights runtime polymorphism and encapsulation of implementation details within the strategy objects.
Key messages in the sequence include:
Client → ConcreteStrategy: new ConcreteStrategy() (instantiation)
Client → Context: setStrategy(ConcreteStrategy) (configuration)
Client → Context: request() (initiation)
Context → Strategy: execute() (delegation)
Strategy → self: performAlgorithm() (execution in concrete class, often shown as an activation bar on the ConcreteStrategy lifeline)
To illustrate switching strategies mid-execution, an extended sequence might show the Client sending another setStrategy(new DifferentConcreteStrategy) after an initial request, followed by a second request() that now delegates to the updated strategy, allowing runtime adaptability without modifying the Context. This is particularly evident in the book's motivational example of varying text justification algorithms, where the Context (e.g., a Composer) can switch between ConcreteStrategies like LeftJustify and CenterJustify based on formatting requirements.[1]
Implementation
Pseudocode
The Strategy pattern is typically implemented through an abstract strategy interface, concrete strategy classes, a context class that delegates to the strategy, and client code that configures and uses the context. This pseudocode provides a language-agnostic outline of the pattern's structure, emphasizing the encapsulation of interchangeable algorithms.[4]
Strategy Interface
The core of the pattern is an abstract Strategy component defining a common interface for all supported algorithms.
interface Strategy {
algorithm(): result;
}
interface Strategy {
algorithm(): result;
}
This interface declares a single abstract method algorithm() that concrete implementations must provide, allowing the context to invoke algorithms uniformly without knowing their specifics.[4]
Concrete Strategies
Concrete strategy classes implement the Strategy interface with specific algorithm logic. For example, two variants might sort data in ascending or descending order.
class ConcreteStrategyA implements Strategy {
algorithm(): result {
// Specific logic for algorithm A, e.g., sort ascending
return "sorted ascending";
}
}
class ConcreteStrategyB implements Strategy {
algorithm(): result {
// Specific logic for algorithm B, e.g., sort descending
return "sorted descending";
}
}
class ConcreteStrategyA implements Strategy {
algorithm(): result {
// Specific logic for algorithm A, e.g., sort ascending
return "sorted ascending";
}
}
class ConcreteStrategyB implements Strategy {
algorithm(): result {
// Specific logic for algorithm B, e.g., sort descending
return "sorted descending";
}
}
Each concrete strategy encapsulates a distinct behavior, enabling runtime selection based on needs.[4]
Context Class
The Context maintains a reference to a Strategy object and delegates algorithm execution to it, promoting flexibility in behavior.
class Context {
private Strategy strategy;
constructor(Strategy initialStrategy) {
this.strategy = initialStrategy;
}
setStrategy(Strategy newStrategy) {
this.strategy = newStrategy;
}
contextMethod(): result {
if (this.strategy == null) {
throw new Error("No strategy assigned");
}
return this.strategy.algorithm();
}
}
class Context {
private Strategy strategy;
constructor(Strategy initialStrategy) {
this.strategy = initialStrategy;
}
setStrategy(Strategy newStrategy) {
this.strategy = newStrategy;
}
contextMethod(): result {
if (this.strategy == null) {
throw new Error("No strategy assigned");
}
return this.strategy.algorithm();
}
}
The context uses a private field for the strategy, with a constructor or setter for assignment. The contextMethod() ensures the strategy is not null before delegation to prevent runtime errors, then invokes the algorithm.[4]
Client Usage
A client instantiates concrete strategies, assigns them to a context, and invokes the context's method to execute the selected algorithm.
class Client {
main() {
strategyA = new [ConcreteStrategyA](/page/Class)();
[context](/page/Context) = new [Context](/page/Context)(strategyA);
resultA = [context](/page/Context).contextMethod(); // Uses ascending sort
strategyB = new [ConcreteStrategyB](/page/Class)();
[context](/page/Context).setStrategy(strategyB);
resultB = [context](/page/Context).contextMethod(); // Switches to descending sort
}
}
class Client {
main() {
strategyA = new [ConcreteStrategyA](/page/Class)();
[context](/page/Context) = new [Context](/page/Context)(strategyA);
resultA = [context](/page/Context).contextMethod(); // Uses ascending sort
strategyB = new [ConcreteStrategyB](/page/Class)();
[context](/page/Context).setStrategy(strategyB);
resultB = [context](/page/Context).contextMethod(); // Switches to descending sort
}
}
This usage demonstrates dynamic strategy interchangeability at runtime, allowing the same context to support varying behaviors without modification.[4]
Language-Specific Example
The Strategy pattern is commonly implemented in Java, an object-oriented language well-suited for demonstrating behavioral design patterns through interfaces and polymorphism. The following example adapts the abstract pseudocode structure to concrete Java syntax, using simple arithmetic operations (addition and subtraction) to show how interchangeable strategies can be applied within a context class.[8]
Strategy Interface
The Strategy interface defines the contract for all concrete strategies, specifying a single method to execute the algorithm with two integer parameters.
java
public interface Strategy {
int execute(int a, int b);
}
public interface Strategy {
int execute(int a, int b);
}
Concrete Strategies
Concrete strategy classes implement the Strategy interface, each encapsulating a specific operation.
Addition Strategy:
java
public class ConcreteAdd implements Strategy {
@Override
public int execute(int a, int b) {
return a + b;
}
}
public class ConcreteAdd implements Strategy {
@Override
public int execute(int a, int b) {
return a + b;
}
}
Subtraction Strategy:
java
public class ConcreteSubtract implements Strategy {
@Override
public int execute(int a, int b) {
return a - b;
}
}
public class ConcreteSubtract implements Strategy {
@Override
public int execute(int a, int b) {
return a - b;
}
}
Context Class
The Context class maintains a reference to a Strategy object and provides methods to set the strategy and delegate execution to it.
java
public class Context {
private Strategy strategy;
public Context() {
// Default strategy can be set here if needed
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
if (strategy == null) {
throw new IllegalStateException("No strategy set");
}
return strategy.execute(a, b);
}
}
public class Context {
private Strategy strategy;
public Context() {
// Default strategy can be set here if needed
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
if (strategy == null) {
throw new IllegalStateException("No strategy set");
}
return strategy.execute(a, b);
}
}
Client Usage
In the client code, a Context instance is created, and different strategies are set and executed at runtime to demonstrate interchangeability. The output shows the results of applying addition and then subtraction to the same inputs.
java
public class Client {
public static void main(String[] args) {
[Context](/page/Context) context = new [Context](/page/Context)();
// Use [addition](/page/Addition) [strategy](/page/Strategy)
[Strategy](/page/Strategy) addStrategy = new ConcreteAdd();
context.setStrategy(addStrategy);
System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); // Output: 10 + 5 = 15
// Switch to subtraction [strategy](/page/Strategy)
[Strategy](/page/Strategy) subtractStrategy = new ConcreteSubtract();
context.setStrategy(subtractStrategy);
System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); // Output: 10 - 5 = 5
}
}
public class Client {
public static void main(String[] args) {
[Context](/page/Context) context = new [Context](/page/Context)();
// Use [addition](/page/Addition) [strategy](/page/Strategy)
[Strategy](/page/Strategy) addStrategy = new ConcreteAdd();
context.setStrategy(addStrategy);
System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); // Output: 10 + 5 = 15
// Switch to subtraction [strategy](/page/Strategy)
[Strategy](/page/Strategy) subtractStrategy = new ConcreteSubtract();
context.setStrategy(subtractStrategy);
System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); // Output: 10 - 5 = 5
}
}
This implementation leverages Java's interface-based polymorphism, allowing the Context to remain agnostic to the specific strategy used, as it interacts solely through the Strategy reference. The code compiles with standard Java compilers (e.g., javac from JDK 8 or later) and runs without additional dependencies, producing the expected output when executed via java Client. Extending the example to include more operations, such as multiplication, would involve adding new classes implementing Strategy without modifying existing code.
Benefits and Trade-offs
Advantages
The Strategy pattern enhances flexibility in software design by enabling the dynamic selection and interchange of algorithms at runtime, without necessitating recompilation or modification of the client code that uses them. This decoupling allows developers to extend functionality by introducing new strategies without altering the core context class, aligning with the open-closed principle by permitting extension while keeping the system open for modification only through new strategy implementations.[9]
By encapsulating each algorithm within its own concrete strategy class, the pattern promotes adherence to the Single Responsibility Principle, as each strategy focuses solely on implementing one specific behavior or algorithm, thereby isolating changes and reducing the risk of unintended side effects in the broader system. This separation ensures that modifications to a particular algorithm affect only its dedicated class, simplifying maintenance and evolution of the codebase.[10]
The pattern facilitates reusability by allowing individual strategy classes to be shared across multiple context classes or even different applications, promoting the creation of modular, interchangeable components that can be composed into various configurations without duplication. This approach leverages object-oriented composition to build families of related behaviors that can be reused independently of their usage context.[9]
Testability is improved through the isolation of strategies, enabling unit tests to target each algorithm independently without the need to instantiate or mock the entire context, which eliminates the complexity of testing conditional logic scattered across multiple if-else branches in monolithic implementations. This modular structure supports more focused and efficient testing practices, as each strategy can be verified in isolation for correctness and performance.[9]
For instance, in a navigation application, the Strategy pattern allows seamless switching between routing algorithms—such as one prioritizing the fastest path versus another favoring scenic routes—by injecting the appropriate strategy into the navigation context at runtime, thereby adapting to user preferences without refactoring the application's core logic.[9]
Disadvantages
The Strategy pattern introduces additional classes, one for each distinct algorithm or behavior, which can increase the overall complexity of the software design by expanding the number of components that must be managed and understood. This proliferation of classes often results in more objects being instantiated at runtime, potentially complicating debugging and maintenance efforts.[11]
A key drawback is the added responsibility placed on the client code, which must be aware of the available strategies and capable of selecting the appropriate one based on context or requirements. This awareness can expose implementation details to the client, potentially leading to tighter coupling or errors if the differences between strategies are not well-documented or intuitive.[4][11]
The pattern incurs a minor performance overhead stemming from dynamic dispatch mechanisms, such as virtual function calls, and the costs associated with creating and switching between strategy objects, which may be negligible in most applications but noticeable in performance-critical scenarios. Additionally, frequent communication between the context and strategy objects can introduce further overhead if not minimized through stateless designs.[11]
For cases involving only a handful of stable algorithms that rarely evolve, the Strategy pattern may overcomplicate the solution unnecessarily, as simpler approaches like conditional statements within the context class would suffice without the added abstraction layers.[4]
In resource-constrained environments, such as embedded systems, the increased number of strategy objects can exacerbate memory usage issues, making the pattern less suitable where efficiency is paramount over flexibility.[11]
Relation to Other Concepts
Open-Closed Principle
The Open-Closed Principle (OCP), introduced by Bertrand Meyer in 1988, states that software entities such as classes, modules, and functions should be open for extension but closed for modification.[12] This principle promotes designs where new functionality can be added through extension mechanisms like inheritance or composition, without altering the source code of existing components, thereby reducing the risk of introducing bugs during modifications.[12]
The Strategy pattern aligns with the OCP by allowing the Context class to remain unchanged when new behaviors are introduced through additional ConcreteStrategy classes.[13] Extension occurs by creating new strategy implementations that adhere to the Strategy interface, enabling the system to incorporate varied algorithms without modifying the core Context logic or existing strategies.[10] This adherence to OCP is evident in scenarios where the pattern encapsulates interchangeable algorithms, such as sorting methods in a data processing library, where new sorting variants can be added independently.[13]
The mechanism supporting this alignment relies on polymorphism and delegation: the Context delegates behavior to a Strategy object via an interface reference, permitting runtime substitution without internal changes to the Context or Client code.[10] For instance, in an e-commerce order processing system, introducing a new payment strategy (e.g., cryptocurrency) involves only defining a new ConcreteStrategy class, leaving the OrderProcessor (Context) unmodified.[13]
However, the Strategy pattern's support for OCP has limitations; while the Context and existing strategies remain closed to modification, Client code may require updates to instantiate and select the new strategies, potentially introducing minor modifications outside the core entities.[14]
Comparison with State Pattern
The State pattern enables an object to alter its behavior when its internal state changes, making it appear as though the object has changed its class; it encapsulates state-specific behavior by delegating to state objects that can trigger transitions between states.[15] In contrast, the Strategy pattern defines a family of interchangeable algorithms, encapsulating each in its own class and allowing a client to select and switch them at runtime without altering the context object.[4] The primary difference lies in intent and binding: Strategy focuses on external selection of independent algorithms, often bound once at initialization for pluggable behaviors, whereas State manages dynamic internal transitions where states may depend on and modify the context's state.[7]
Both patterns share structural similarities as behavioral designs from the Gang of Four catalog, relying on composition and polymorphism: a context delegates varying behavior to interchangeable objects via a common interface, promoting loose coupling and adherence to the open-closed principle.[15] However, Strategy treats algorithms as stateless and unaware of each other, avoiding interdependencies, while State introduces potential cycles or dependencies as states can invoke context methods to change the current state.[16]
Choose the Strategy pattern when the goal is to support multiple, interchangeable algorithms independent of the object's state, such as selecting different sorting methods in a data processor.[4] Opt for the State pattern when behavior inherently depends on the object's lifecycle or internal conditions, like managing play, pause, and stop states in a media player where each state dictates valid transitions.[15] For instance, a file compression tool might use Strategy to swap algorithms like ZIP or GZIP externally based on user preference, without internal state changes, whereas a TCP connection handler employs State to evolve through listening, established, and closed phases, with each phase restricting actions.[7]
Relation to Flyweight Pattern
The Flyweight pattern is a structural design pattern that minimizes memory usage by sharing as much data as possible between similar objects. The Strategy pattern relates to Flyweight when strategies are stateless and can be shared among multiple contexts to optimize resource usage. In the Gang of Four's description, shared strategies act as flyweights if they do not maintain state across invocations.[17] This combination is useful in scenarios with many contexts using the same algorithm, such as in graphics rendering where common rendering strategies are reused across numerous objects.
Comparison with Template Method Pattern
The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class, allowing subclasses to override specific steps without changing the overall structure. In contrast to Strategy, which uses composition to allow full runtime replacement of algorithms, Template Method relies on inheritance for compile-time variation of algorithm parts.[18] Strategy provides greater flexibility for switching entire behaviors dynamically, while Template Method ensures a fixed sequence with customizable hooks, making it suitable for frameworks where the algorithm outline is invariant but details vary.[19]