The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.
Design Principle
Encapsulate what varies: identify the aspects of your application that vary and seperate them from what stays the same.
Program to an interface, not an implementation (really means "Program to a supertype").
Favor composition over inheritance.
Example codes
Info
We are using the Strategy Pattern to implement the various behaviours of the ducks. This means that the duck behaviour
has been encapsulated into its own set of classes that can be easily expanded and changed, even at runtime if needed.
fromabcimportABC,abstractmethodclassFlyBehaviour(ABC):@abstractmethoddeffly(self):passclassFlyWithWings(FlyBehaviour):deffly(self):print("Flying with wings")classFlyNoWay(FlyBehaviour):deffly(self):print("Cannot fly")classQuackBehaviour(ABC):@abstractmethoddefquack(self):passclassQuack(QuackBehaviour):defquack(self):print("Quack")classSqueak(QuackBehaviour):defquack(self):print("Squeak")classMuteQuack(QuackBehaviour):defquack(self):passclassDuck:def__init__(self,fly_behaviour,quack_behaviour):self.fly_behaviour=fly_behaviourself.quack_behaviour=quack_behaviourdefset_fly_behaviour(self,fly_behaviour):self.fly_behaviour=fly_behaviourdefset_quack_behaviour(self,quack_behaviour):self.quack_behaviour=quack_behaviourdefperform_quack(self):self.quack_behaviour.quack()defperform_fly(self):self.fly_behaviour.fly()@staticmethoddefswim(self):print("Swimming")defdisplay(self):passclassMallardDuck(Duck):def__init__(self):super().__init__(FlyWithWings(),Quack())defdisplay(self):print("I'm a Mallard Duck")classRedheadDuck(Duck):def__init__(self):super().__init__(FlyWithWings(),Quack())defdisplay(self):print("I'm a Redhead Duck")classRubberDuck(Duck):def__init__(self):super().__init__(FlyNoWay(),Squeak())defdisplay(self):print("I'm a Rubber Duck")classDecoyDuck(Duck):def__init__(self):super().__init__(FlyNoWay(),MuteQuack())defdisplay(self):print("I'm a Decoy Duck")defmain():mallard=MallardDuck()mallard.display()# Output: I'm a Mallard Duckmallard.perform_quack()# Output: Quackmallard.perform_fly()# Output: Flying with wings# Change the fly behavior to FlyNoWaymallard.set_fly_behaviour(FlyNoWay())mallard.perform_fly()# Output: Cannot fly# Change the quack behavior to Squeakmallard.set_quack_behaviour(Squeak())mallard.perform_quack()if__name__=='__main__':main()
packagemainimport"fmt"// FlyBehavior is an interface that defines the fly behaviortypeFlyBehaviorinterface{fly()}// FlyWithWings implements the FlyBehavior interfacetypeFlyWithWingsstruct{}func(f*FlyWithWings)fly(){fmt.Println("Flying with wings")}// FlyNoWay implements the FlyBehavior interfacetypeFlyNoWaystruct{}func(f*FlyNoWay)fly(){fmt.Println("Cannot fly")}// QuackBehavior is an interface that defines the quack behaviortypeQuackBehaviorinterface{quack()}// Quack implements the QuackBehavior interfacetypeQuackstruct{}func(q*Quack)quack(){fmt.Println("Quack")}// Squeak implements the QuackBehavior interfacetypeSqueakstruct{}func(s*Squeak)quack(){fmt.Println("Squeak")}// MuteQuack implements the QuackBehavior interfacetypeMuteQuackstruct{}func(m*MuteQuack)quack(){// No output}// Duck is the base struct that holds the fly and quack behaviorstypeDuckstruct{flyBehaviorFlyBehaviorquackBehaviorQuackBehavior}func(d*Duck)setFlyBehavior(fbFlyBehavior){d.flyBehavior=fb}func(d*Duck)setQuackBehavior(qbQuackBehavior){d.quackBehavior=qb}func(d*Duck)performQuack(){d.quackBehavior.quack()}func(d*Duck)performFly(){d.flyBehavior.fly()}func(d*Duck)swim(){fmt.Println("Swimming")}func(d*Duck)display(){// Base implementation does nothing}// MallardDuck is a concrete implementation of DucktypeMallardDuckstruct{Duck}func(m*MallardDuck)display(){fmt.Println("I'm a Mallard Duck")}// RedheadDuck is a concrete implementation of DucktypeRedheadDuckstruct{Duck}func(r*RedheadDuck)display(){fmt.Println("I'm a Redhead Duck")}// RubberDuck is a concrete implementation of DucktypeRubberDuckstruct{Duck}func(r*RubberDuck)display(){fmt.Println("I'm a Rubber Duck")}// DecoyDuck is a concrete implementation of DucktypeDecoyDuckstruct{Duck}func(d*DecoyDuck)display(){fmt.Println("I'm a Decoy Duck")}funcmain(){mallard:=&MallardDuck{Duck{&FlyWithWings{},&Quack{}}}mallard.display()// Output: I'm a Mallard Duckmallard.performQuack()// Output: Quackmallard.performFly()// Output: Flying with wings// Change the fly behavior to FlyNoWaymallard.setFlyBehavior(&FlyNoWay{})mallard.performFly()// Output: Cannot fly// Change the quack behavior to Squeakmallard.setQuackBehavior(&Squeak{})mallard.performQuack()// Output: Squeak}
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all
its dependents are notified and updated automatically.
Design Principle
Strive for loosely coupled designs betwwen objects that interacts.
Subjects update Observers using a common interface. Observers of any concrete type can participate in the pattern
as long as they implement the Observer interace.
Observers are loosely coupled in that the Subject knows nothing about them, other thant that they implement the
Observer interface.
fromcontextlibimportsuppressfromtypingimportProtocolclassObserver(Protocol):defupdate(self,subject)->None:passclassSubject:def__init__(self):self._observers=[]defregister_observer(self,observer:Observer)->None:ifobservernotinself._observers:self._observers.append(observer)defremove_observer(self,observer:Observer)->None:withsuppress(ValueError):self._observers.remove(observer)defnotify(self):forobserverinself._observers:observer.update(self)classWeatherData(Subject):def__init__(self):super().__init__()self._temperature=0self._humidity=0@propertydeftemperature(self)->int:returnself._temperature@temperature.setterdeftemperature(self,value:int)->None:self._temperature=valueself.notify()@propertydefhumidity(self)->int:returnself._humidity@humidity.setterdefhumidity(self,value:int)->None:self._humidity=valueself.notify()classDisplayElement(Protocol):defdisplay(self):passclassCurrentConditionsDisplay(Observer,DisplayElement):def__init__(self,weather_data:WeatherData):self._weather_data=weather_dataself._weather_data.register_observer(self)self._temperature=0self._humidity=0defupdate(self,subject:Subject)->None:self._temperature=self._weather_data.temperatureself._humidity=self._weather_data.humidityself.display()defdisplay(self):print(f'Current conditions: {self._temperature}°C, {self._humidity}% humidity')classStatisticsDisplay(Observer,DisplayElement):def__init__(self,weather_data:WeatherData):self._weather_data=weather_dataself._weather_data.register_observer(self)self._temperature=0self._temp_sum=0self._num_readings=0defupdate(self,subject:Subject)->None:self._temperature=self._weather_data.temperatureself._temp_sum+=self._temperatureself._num_readings+=1self.display()defdisplay(self):print(f'Statistics: Average temp: {self._temp_sum/self._num_readings}')defmain():weather_data=WeatherData()current_condition_display=CurrentConditionsDisplay(weather_data)StatisticsDisplay(weather_data)weather_data.temperature=10# Current conditions: 10°C, 0% humidity Statistics: Average temp: 10.0weather_data.temperature=20# Current conditions: 20°C, 0% humidity Statistics: Average temp: 15.0weather_data.temperature=30# Current conditions: 30°C, 0% humidity Statistics: Average temp: 20.0weather_data.humidity=40# Current conditions: 30°C, 40% humidity Statistics: Average temp: 22.5weather_data.remove_observer(current_condition_display)weather_data.temperature=50# Statistics: Average temp: 28.0if__name__=='__main__':main()