Strategy Pattern
Issue
Pure subclassing approach:
classDiagram Duck <|-- CityDuck Duck <|-- WildDuck Duck <|-- RubberDuck class Duck{ +quack() +display() +fly() } class CityDuck{ +display() +fly() } class WildDuck{ +display() +fly() } class RubberDuck{ +display() +fly() }
Each sub-class of the parent class (Duck) potentially implements any method differently than the parent class (no code reuse)
Multiple sub-classes might share common implementations of the same method but not the one from the parent class (e.g. fly()) -> code duplication
Letting sub-classes inherit method implementations from another class is not a good idea (-> multiple inheritance “Deadly Diamond Of Death”) as it might lead to conflicts. Moreover, there are combinations of implementations which cannot be established over pure inheritance
Design Principles & Definition
Principle 1
Program to an interface, not an implementation
Never create instances of implementing classes, but instead refer to an interface
Each interface sets a blueprint for its implementing subclasses, which do the actual implementation
When the class, holding references to these interfaces in form of class variables, itself is subclassed, then the children classes assign individual implementations of the interface to these class variables
Principle 2
Favor composition over inheritance
Composition means building a HAS-A relationship with a class, which cannot be instantiated, either an abstract class or an interface
Sub-classes that have a composition relationship allows them to select an interface implementation during runtime
Defining behaviours during runtime occurs when the sub-class instantiates an interface implementation and assigns it to the inherited class variable of the interface type
Definition
The Strategy Patter defines a family of algorithms, encapsulate each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Appliance
classDiagram FlyBehavior <-- Duck FlyBehavior <|-- SimpleFly FlyBehavior <|-- NoFly Duck <|-- CityDuck Duck <|-- WildDuck Duck <|-- RubberDuck class FlyBehavior{ <<interface>> +fly() } class SimpleFly{ +fly() } class NoFly{ +fly() } class Duck{ +quack() +display() +fly() } class CityDuck{ +display() +fly() } class WildDuck{ +display() +fly() } class RubberDuck{ +display() +fly() }
Here, the various fly() method implementation are moved from the duck sub-classes and put into proper classes, that implement the FlyBehavior interface
Same can be done with the quack() or the display() method, if required
That way, each sub-class of Duck has the desired behavior without implementing any of it themselves
- Context:
Parent class for all Client classes
- Client:
Subclass of the Context class
- Strategy:
Interface class for all Strategy implementations
Find a template example for Python at https://refactoring.guru/design-patterns/strategy/python/example
Taking it one step further, it is also possible to refrain from using actual Clients, but instead instantiate the Context class (here: Duck) with passing the wanted behavior into the constructor (dependency injection):
from abc import ABC, abstractmethod
class Duck:
def __init__(self,
fly_behavior: FlyBehavior,
quack_behavior: QuackBehavior):
self.fly_behavior = fly_behavior
self.quack_behavior = quack_behavior
def fly():
self.fly_behavior.fly()
def quack():
self.quack_behavior.quack()
class FlyBehavior(ABC):
@abstractmethod
def fly():
pass
class QuackBehavior(ABC):
@abstractmethod
def quack():
pass
# some classes implementing FlyBehavior & QuackBehavior
# e.g. FlyNoWay, QuackNoWay
if __name__ == "__main__":
no_use_duck = Duck(fly_behavior=FlyNoWay(), quack_behavior=QuackNoWay())
no_use_duck.fly()
no_use_duck.quack()