Strategy Pattern
Introduction
The Strategy pattern is a behavioral design pattern that enables selecting an algorithm’s behavior at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.
Purpose
- To define a family of algorithms, encapsulate each one, and make them interchangeable.
- To let the algorithm vary independently from clients that use it.
Use Cases
- When there are many related classes that only differ in their behavior.
- For algorithms used in a class defined in many subclasses, providing an alternative to conditional statements.
- To manage the complexities of algorithms used and the clarity of the overall system.
Advantages/Disadvantages
Advantages:
- A family of related algorithms can be defined and maintained in each derived class.
- Provides an alternative to conditional statements for selecting desired behavior.
- Supports the Open/Closed Principle.
Disadvantages:
- The number of objects increases as new strategies are introduced.
- Clients must be aware of the differences between strategies to select the right one.
Implementation in JavaScript
class Strategy {
execute() {}
}
class ConcreteStrategyA extends Strategy {
execute() {
console.log('Implementing Strategy A');
}
}
class ConcreteStrategyB extends Strategy {
execute() {
console.log('Implementing Strategy B');
}
}
class Context {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
executeStrategy() {
this.strategy.execute();
}
}
// Usage
const strategyA = new ConcreteStrategyA();
const strategyB = new ConcreteStrategyB();
const context = new Context(strategyA);
context.executeStrategy(); // Implementing Strategy A
context.setStrategy(strategyB);
context.executeStrategy(); // Implementing Strategy B
Practical Example
Consider a backend application where you need different logging strategies (e.g., logging to console, logging to a file) based on the runtime environment or user preference.
class LoggingStrategy {
log(message) {}
}
class ConsoleLoggingStrategy extends LoggingStrategy {
log(message) {
console.log(`Console: ${message}`);
}
}
class FileLoggingStrategy extends LoggingStrategy {
log(message) {
// Simulate file logging
console.log(`File: ${message}`);
}
}
class Logger {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
log(message) {
this.strategy.log(message);
}
}
// Usage
const consoleStrategy = new ConsoleLoggingStrategy();
const fileStrategy = new FileLoggingStrategy();
const logger = new Logger(consoleStrategy);
logger.log('Logging to console'); // Logs to console
logger.setStrategy(fileStrategy);
logger.log('Logging to file'); // Simulates logging to a file
In this example, Logger
uses different LoggingStrategy
objects (like ConsoleLoggingStrategy
and FileLoggingStrategy
) to change the logging behavior dynamically. This design allows easy swapping of the logging method without altering the logger class.