"""Module for implementing the "Strategy" pattern.
Strategy is a behavioral design pattern that defines a family of similar algorithms and
puts each of them in its own class, after which the algorithms can be interchanged right
during program execution.
1. The context stores a reference to the object of a specific strategy, working with it
through the general strategy interface.
2. The strategy defines an interface common to all variations of the algorithm.
The context uses this interface to invoke the algorithm.
For context, it doesn't matter which variation of the algorithm is chosen, since they
all have the same interface.
3. Specific strategies implement different variations of the algorithm.
4. During the execution of the program, the context receives calls from the client and
delegates them to the object of a specific strategy.
5. The client must create an object of a specific strategy and pass it to the context
constructor. In addition, the client should be able to replace the strategy on the fly
using a setter. Due to this, the context will not know which strategy is currently
selected.
Developed by: Viacheslav Kolupaev, https://vkolupaev.com/
"""
# Standard Library
from abc import (
ABC,
abstractmethod,
)
class Strategy(ABC):
"""Strategy interface.
The Strategy interface declares operations common to all supported versions of some
algorithm.
The Context uses this interface to call the algorithm defined by Concrete Strategies.
"""
@abstractmethod
def execute(self, input_data: dict[str, str]) -> dict[str, str]:
"""Execute the operation using a Strategy."""
raise NotImplementedError
class Context(object):
"""The Context defines the interface of interest to clients."""
@property
def strategy(self) -> Strategy:
"""The Context maintains a reference to one of the Strategy objects.
The Context does not know the concrete class of a strategy. It should
work with all strategies via the Strategy interface.
Returns:
Reference to one of the Strategy objects.
"""
return self._strategy
@strategy.setter
def strategy(self, strategy: Strategy) -> None:
"""Usually, the Context allows replacing a Strategy object at runtime."""
self._strategy = strategy
def execute_strategy(
self,
input_data: dict[str, str],
) -> dict[str, str]:
"""Execute the operation using a Strategy.
The Context delegates some work to the Strategy object instead of
implementing multiple versions of the algorithm on its own.
Args:
input_data: Parsed data from the request to execute the strategy.
Returns:
The result of strategy execution.
"""
return self._strategy.execute(input_data=input_data)
class ConcreteStrategyA(Strategy):
def execute(self, input_data: dict[str, str]) -> dict[str, str]:
return {
'key': 'value_processed_by_strategy_a',
}
class ConcreteStrategyB(Strategy):
def execute(self, input_data: dict[str, str]) -> dict[str, str]:
return {
'key': 'value_processed_by_strategy_b',
}
class ConcreteStrategyC(Strategy):
def execute(self, input_data: dict[str, str]) -> dict[str, str]:
return {
'key': 'value_processed_by_strategy_c',
}
def client_code() -> None:
"""Client code.
The client code picks a concrete strategy and passes it to the context.
The client should be aware of the differences between strategies in order to
make the right choice.
"""
# 1. Create a context object.
context = Context()
# 2. Get arguments for calling a strategy operation.
input_data = {
'key': 'value',
}
# 3. Get the desired operation.
switch_a = True
switch_b = False
switch_c = False
# 4. Choose a strategy.
if switch_a:
context.strategy = ConcreteStrategyA()
elif switch_b:
context.strategy = ConcreteStrategyB()
elif switch_c:
context.strategy = ConcreteStrategyC()
else:
raise ValueError
# 5. Perform an operation using a strategy.
strategy_execution_result = context.execute_strategy(input_data=input_data)
# 6. Display the result on the screen.
print(strategy_execution_result)
if __name__ == "__main__":
client_code()