Behavioral patterns take care of effective communication and the assignment of responsibilities between objects.
Code taken fully from refactoring.guru with my descriptions. Again, no code was written by me, I am just able to execute it here.
Chain of Responsibility
Passes request down the chain, until it is handled.
from __future__ import annotationsfrom abc import ABC, abstractmethodfrom typing import Any, Optionalclass Handler(ABC):""" The Handler interface declares a method for building the chain of handlers. It also declares a method for executing a request. """@abstractmethoddef set_next(self, handler: Handler) -> Handler:pass@abstractmethoddef handle(self, request) -> Optional[str]:passclass AbstractHandler(Handler):""" The default chaining behavior can be implemented inside a base handler class. """ _next_handler: Handler =Nonedef set_next(self, handler: Handler) -> Handler:self._next_handler = handler# Returning a handler from here will let us link handlers in a# convenient way like this:# monkey.set_next(squirrel).set_next(dog)return handler@abstractmethoddef handle(self, request: Any) ->str:ifself._next_handler:returnself._next_handler.handle(request)returnNone"""All Concrete Handlers either handle a request or pass it to the next handler inthe chain."""class MonkeyHandler(AbstractHandler):def handle(self, request: Any) ->str:if request =="Banana":returnf"Monkey: I'll eat the {request}"else:returnsuper().handle(request)class SquirrelHandler(AbstractHandler):def handle(self, request: Any) ->str:if request =="Nut":returnf"Squirrel: I'll eat the {request}"else:returnsuper().handle(request)class DogHandler(AbstractHandler):def handle(self, request: Any) ->str:if request =="MeatBall":returnf"Dog: I'll eat the {request}"else:returnsuper().handle(request)def client_code(handler: Handler) ->None:""" The client code is usually suited to work with a single handler. In most cases, it is not even aware that the handler is part of a chain. """for food in ["Nut", "Banana", "Cup of coffee"]:print(f"\nClient: Who wants a {food}?") result = handler.handle(food)if result:print(f" {result}", end="")else:print(f" {food} was left untouched.", end="")if__name__=="__main__": monkey = MonkeyHandler() squirrel = SquirrelHandler() dog = DogHandler() monkey.set_next(squirrel).set_next(dog) # KEY POINT# The client should be able to send a request to any handler, not just the# first one in the chain.print("Chain: Monkey > Squirrel > Dog") client_code(monkey)print("\n")print("Subchain: Squirrel > Dog") client_code(squirrel)
Chain: Monkey > Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut
Client: Who wants a Banana?
Monkey: I'll eat the Banana
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Subchain: Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut
Client: Who wants a Banana?
Banana was left untouched.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Command
Command can either execute simple stuff or delegate to receiver more complex work.
from __future__ import annotationsfrom abc import ABC, abstractmethodclass Command(ABC):""" The Command interface declares a method for executing a command. """@abstractmethoddef execute(self) ->None:passclass SimpleCommand(Command):""" Some commands can implement simple operations on their own. """def__init__(self, payload: str) ->None:self._payload = payloaddef execute(self) ->None:print(f"SimpleCommand: See, I can do simple things like printing"f"({self._payload})")class ComplexCommand(Command):""" However, some commands can delegate more complex operations to other objects, called "receivers." """def__init__(self, receiver: Receiver, a: str, b: str) ->None:""" Complex commands can accept one or several receiver objects along with any context data via the constructor. """self._receiver = receiverself._a = aself._b = bdef execute(self) ->None:""" Commands can delegate to any methods of a receiver. """print("ComplexCommand: Complex stuff should be done by a receiver object", end="")self._receiver.do_something(self._a) # KEY POINTself._receiver.do_something_else(self._b)class Receiver:""" The Receiver classes contain some important business logic. They know how to perform all kinds of operations, associated with carrying out a request. In fact, any class may serve as a Receiver. """def do_something(self, a: str) ->None:print(f"\nReceiver: Working on ({a}.)", end="")def do_something_else(self, b: str) ->None:print(f"\nReceiver: Also working on ({b}.)", end="")class Invoker:""" The Invoker is associated with one or several commands. It sends a request to the command. """ _on_start =None _on_finish =None""" Initialize commands. """def set_on_start(self, command: Command):self._on_start = commanddef set_on_finish(self, command: Command):self._on_finish = commanddef do_something_important(self) ->None:""" The Invoker does not depend on concrete command or receiver classes. The Invoker passes a request to a receiver indirectly, by executing a command. """print("Invoker: Does anybody want something done before I begin?")ifisinstance(self._on_start, Command):self._on_start.execute()print("Invoker: ...doing something really important...")print("Invoker: Does anybody want something done after I finish?")ifisinstance(self._on_finish, Command):self._on_finish.execute()if__name__=="__main__":""" The client code can parameterize an invoker with any commands. """ invoker = Invoker() invoker.set_on_start(SimpleCommand("Say Hi!")) invoker.set_on_finish(ComplexCommand( Receiver(), "Send email", "Save report")) invoker.do_something_important()
Invoker: Does anybody want something done before I begin?
SimpleCommand: See, I can do simple things like printing(Say Hi!)
Invoker: ...doing something really important...
Invoker: Does anybody want something done after I finish?
ComplexCommand: Complex stuff should be done by a receiver object
Receiver: Working on (Send email.)
Receiver: Also working on (Save report.)
Iterator
Defines how to iterate over a collection.
from __future__ import annotationsfrom collections.abc import Iterable, Iteratorfrom typing import Any"""To create an iterator in Python, there are two abstract classes from the built-in `collections` module - Iterable,Iterator. # KEY POINTWe need to implement the `__iter__()` method in the iterated object (collection), and the `__next__ ()` method in the iterator."""class AlphabeticalOrderIterator(Iterator):""" Concrete Iterators implement various traversal algorithms. These classes store the current traversal position at all times. """""" `_position` attribute stores the current traversal position. An iterator may have a lot of other fields for storing iteration state, especially when it is supposed to work with a particular kind of collection. """ _position: int=None""" This attribute indicates the traversal direction. """ _reverse: bool=Falsedef__init__(self, collection: WordsCollection, reverse: bool=False) ->None:self._collection = collectionself._reverse = reverseself._position =-1if reverse else0def__next__(self) -> Any:""" The __next__() method must return the next item in the sequence. On reaching the end, and in subsequent calls, it must raise StopIteration. """try: value =self._collection[self._position]self._position +=-1ifself._reverse else1exceptIndexError:raiseStopIteration()return valueclass WordsCollection(Iterable):""" Concrete Collections provide one or several methods for retrieving fresh iterator instances, compatible with the collection class. """def__init__(self, collection: list[Any] |None=None) ->None:self._collection = collection or []def__getitem__(self, index: int) -> Any:returnself._collection[index]def__iter__(self) -> AlphabeticalOrderIterator:""" The __iter__() method returns the iterator object itself, by default we return the iterator in ascending order. """return AlphabeticalOrderIterator(self)def get_reverse_iterator(self) -> AlphabeticalOrderIterator:return AlphabeticalOrderIterator(self, True)def add_item(self, item: Any) ->None:self._collection.append(item)if__name__=="__main__":# The client code may or may not know about the Concrete Iterator or# Collection classes, depending on the level of indirection you want to keep# in your program. collection = WordsCollection() collection.add_item("First") collection.add_item("Second") collection.add_item("Third")print("Straight traversal:")print("\n".join(collection))print("")print("Reverse traversal:")print("\n".join(collection.get_reverse_iterator()), end="")
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First
Mediator
Enables communication between objects.
from __future__ import annotationsfrom abc import ABCclass Mediator(ABC):""" The Mediator interface declares a method used by components to notify the mediator about various events. The Mediator may react to these events and pass the execution to other components. """def notify(self, sender: object, event: str) ->None:passclass ConcreteMediator(Mediator):def__init__(self, component1: Component1, component2: Component2) ->None:self._component1 = component1self._component1.mediator =selfself._component2 = component2self._component2.mediator =selfdef notify(self, sender: object, event: str) ->None:if event =="A":print("Mediator reacts on A and triggers following operations:")self._component2.do_c()elif event =="D":print("Mediator reacts on D and triggers following operations:")self._component1.do_b()self._component2.do_c()class BaseComponent:""" The Base Component provides the basic functionality of storing a mediator's instance inside component objects. """def__init__(self, mediator: Mediator =None) ->None:self._mediator = mediator@propertydef mediator(self) -> Mediator:returnself._mediator@mediator.setterdef mediator(self, mediator: Mediator) ->None:self._mediator = mediator"""Concrete Components implement various functionality. They don't depend on othercomponents. They also don't depend on any concrete mediator classes."""class Component1(BaseComponent):def do_a(self) ->None:print("Component 1 does A.")self.mediator.notify(self, "A")def do_b(self) ->None:print("Component 1 does B.")self.mediator.notify(self, "B")class Component2(BaseComponent):def do_c(self) ->None:print("Component 2 does C.")self.mediator.notify(self, "C")def do_d(self) ->None:print("Component 2 does D.")self.mediator.notify(self, "D")if__name__=="__main__":# The client code. c1 = Component1() c2 = Component2() mediator = ConcreteMediator(c1, c2) # KEY POINTprint("Client triggers operation A.") c1.do_a()print("\n", end="")print("Client triggers operation D.") c2.do_d()
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
Memento
Save a state, when you want to undo an action.
from __future__ import annotationsfrom abc import ABC, abstractmethodfrom datetime import datetimefrom random import samplefrom string import ascii_lettersclass Originator:""" The Originator holds some important state that may change over time. It also defines a method for saving the state inside a memento and another method for restoring the state from it. """ _state =None""" For the sake of simplicity, the originator's state is stored inside a single variable. """def__init__(self, state: str) ->None:self._state = stateprint(f"Originator: My initial state is: {self._state}")def do_something(self) ->None:""" The Originator's business logic may affect its internal state. Therefore, the client should backup the state before launching methods of the business logic via the save() method. """print("Originator: I'm doing something important.")self._state =self._generate_random_string(30)print(f"Originator: and my state has changed to: {self._state}")@staticmethoddef _generate_random_string(length: int=10) ->str:return"".join(sample(ascii_letters, length))def save(self) -> Memento:""" Saves the current state inside a memento. """return ConcreteMemento(self._state) # KEY POINTdef restore(self, memento: Memento) ->None:""" Restores the Originator's state from a memento object. """self._state = memento.get_state()print(f"Originator: My state has changed to: {self._state}")class Memento(ABC):""" The Memento interface provides a way to retrieve the memento's metadata, such as creation date or name. However, it doesn't expose the Originator's state. """@abstractmethoddef get_name(self) ->str:pass@abstractmethoddef get_date(self) ->str:passclass ConcreteMemento(Memento):def__init__(self, state: str) ->None:self._state = stateself._date =str(datetime.now())[:19]def get_state(self) ->str:""" The Originator uses this method when restoring its state. """returnself._statedef get_name(self) ->str:""" The rest of the methods are used by the Caretaker to display metadata. """returnf"{self._date} / ({self._state[0:9]}...)"def get_date(self) ->str:returnself._dateclass Caretaker:""" The Caretaker doesn't depend on the Concrete Memento class. Therefore, it doesn't have access to the originator's state, stored inside the memento. It works with all mementos via the base Memento interface. """def__init__(self, originator: Originator) ->None:self._mementos = []self._originator = originatordef backup(self) ->None:print("\nCaretaker: Saving Originator's state...")self._mementos.append(self._originator.save())def undo(self) ->None:ifnotlen(self._mementos):return memento =self._mementos.pop()print(f"Caretaker: Restoring state to: {memento.get_name()}")try:self._originator.restore(memento)exceptException:self.undo()def show_history(self) ->None:print("Caretaker: Here's the list of mementos:")for memento inself._mementos:print(memento.get_name())if__name__=="__main__": originator = Originator("Super-duper-super-duper-super.") caretaker = Caretaker(originator) caretaker.backup() originator.do_something() caretaker.backup() originator.do_something() caretaker.backup() originator.do_something()print() caretaker.show_history()print("\nClient: Now, let's rollback!\n") caretaker.undo()print("\nClient: Once more!\n") caretaker.undo()
Originator: My initial state is: Super-duper-super-duper-super.
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: MkTDBwnJEdosCOpXrgbfYWeLAUGRza
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: lcGDuJvmHLdaCpkWKSiBIjneXAftxs
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: kRxDmusAWhjBJyngaINObrTKPzYwvE
Caretaker: Here's the list of mementos:
2024-03-19 16:25:42 / (Super-dup...)
2024-03-19 16:25:42 / (MkTDBwnJE...)
2024-03-19 16:25:42 / (lcGDuJvmH...)
Client: Now, let's rollback!
Caretaker: Restoring state to: 2024-03-19 16:25:42 / (lcGDuJvmH...)
Originator: My state has changed to: lcGDuJvmHLdaCpkWKSiBIjneXAftxs
Client: Once more!
Caretaker: Restoring state to: 2024-03-19 16:25:42 / (MkTDBwnJE...)
Originator: My state has changed to: MkTDBwnJEdosCOpXrgbfYWeLAUGRza
Observer
It enables subscription, so when the state of the subject changes, the observer is notified and can react.
from __future__ import annotationsfrom abc import ABC, abstractmethodfrom random import randrangefrom typing import Listclass Subject(ABC):""" The Subject interface declares a set of methods for managing subscribers. """@abstractmethoddef attach(self, observer: Observer) ->None:""" Attach an observer to the subject. """pass@abstractmethoddef detach(self, observer: Observer) ->None:""" Detach an observer from the subject. """pass@abstractmethoddef notify(self) ->None:""" Notify all observers about an event. """passclass ConcreteSubject(Subject):""" The Subject owns some important state and notifies observers when the state changes. """ _state: int=None""" For the sake of simplicity, the Subject's state, essential to all subscribers, is stored in this variable. """ _observers: List[Observer] = []""" List of subscribers. In real life, the list of subscribers can be stored more comprehensively (categorized by event type, etc.). """def attach(self, observer: Observer) ->None:print("Subject: Attached an observer.")self._observers.append(observer) # KEY POINTdef detach(self, observer: Observer) ->None:self._observers.remove(observer)""" The subscription management methods. """def notify(self) ->None:""" Trigger an update in each subscriber. """print("Subject: Notifying observers...")for observer inself._observers: observer.update(self)def some_business_logic(self) ->None:""" Usually, the subscription logic is only a fraction of what a Subject can really do. Subjects commonly hold some important business logic, that triggers a notification method whenever something important is about to happen (or after it). """print("\nSubject: I'm doing something important.")self._state = randrange(0, 10)print(f"Subject: My state has just changed to: {self._state}")self.notify()class Observer(ABC):""" The Observer interface declares the update method, used by subjects. """@abstractmethoddef update(self, subject: Subject) ->None:""" Receive update from subject. """pass"""Concrete Observers react to the updates issued by the Subject they had beenattached to."""class ConcreteObserverA(Observer):def update(self, subject: Subject) ->None:if subject._state <3:print("ConcreteObserverA: Reacted to the event")class ConcreteObserverB(Observer):def update(self, subject: Subject) ->None:if subject._state ==0or subject._state >=2:print("ConcreteObserverB: Reacted to the event")if__name__=="__main__":# The client code. subject = ConcreteSubject() observer_a = ConcreteObserverA() subject.attach(observer_a) observer_b = ConcreteObserverB() subject.attach(observer_b) subject.some_business_logic() subject.some_business_logic() subject.detach(observer_a) subject.some_business_logic()
Subject: Attached an observer.
Subject: Attached an observer.
Subject: I'm doing something important.
Subject: My state has just changed to: 9
Subject: Notifying observers...
ConcreteObserverB: Reacted to the event
Subject: I'm doing something important.
Subject: My state has just changed to: 1
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event
Subject: I'm doing something important.
Subject: My state has just changed to: 5
Subject: Notifying observers...
ConcreteObserverB: Reacted to the event
State
Records states of a Context and transitions between the states. State knows about Context and vice versa.
from __future__ import annotationsfrom abc import ABC, abstractmethodclass Context:""" The Context defines the interface of interest to clients. It also maintains a reference to an instance of a State subclass, which represents the current state of the Context. """ _state =None""" A reference to the current state of the Context. """def__init__(self, state: State) ->None:self.transition_to(state)def transition_to(self, state: State): # KEY POINT""" The Context allows changing the State object at runtime. """print(f"Context: Transition to {type(state).__name__}")self._state = stateself._state.context =self""" The Context delegates part of its behavior to the current State object. """def request1(self):self._state.handle1()def request2(self):self._state.handle2()class State(ABC):""" The base State class declares methods that all Concrete State should implement and also provides a backreference to the Context object, associated with the State. This backreference can be used by States to transition the Context to another State. """@propertydef context(self) -> Context:returnself._context@context.setterdef context(self, context: Context) ->None:self._context = context@abstractmethoddef handle1(self) ->None:pass@abstractmethoddef handle2(self) ->None:pass"""Concrete States implement various behaviors, associated with a state of theContext."""class ConcreteStateA(State):def handle1(self) ->None:print("ConcreteStateA handles request1.")print("ConcreteStateA wants to change the state of the context.")self.context.transition_to(ConcreteStateB())def handle2(self) ->None:print("ConcreteStateA handles request2.")class ConcreteStateB(State):def handle1(self) ->None:print("ConcreteStateB handles request1.")def handle2(self) ->None:print("ConcreteStateB handles request2.")print("ConcreteStateB wants to change the state of the context.")self.context.transition_to(ConcreteStateA())if__name__=="__main__":# The client code. context = Context(ConcreteStateA()) context.request1() context.request2()
Context: Transition to ConcreteStateA
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA
Strategy
Strategy is a way to select an algorithm at runtime.
from __future__ import annotationsfrom abc import ABC, abstractmethodfrom typing import Listclass Context():""" The Context defines the interface of interest to clients. """def__init__(self, strategy: Strategy) ->None: # KEY POINT""" Usually, the Context accepts a strategy through the constructor, but also provides a setter to change it at runtime. """self._strategy = strategy@propertydef 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. """returnself._strategy@strategy.setterdef strategy(self, strategy: Strategy) ->None:""" Usually, the Context allows replacing a Strategy object at runtime. """self._strategy = strategydef do_some_business_logic(self) ->None:""" The Context delegates some work to the Strategy object instead of implementing multiple versions of the algorithm on its own. """# ...print("Context: Sorting data using the strategy (not sure how it'll do it)") result =self._strategy.do_algorithm(["a", "b", "c", "d", "e"])print(",".join(result))# ...class Strategy(ABC):""" 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. """@abstractmethoddef do_algorithm(self, data: List):pass"""Concrete Strategies implement the algorithm while following the base Strategyinterface. The interface makes them interchangeable in the Context."""class ConcreteStrategyA(Strategy):def do_algorithm(self, data: List) -> List:returnsorted(data)class ConcreteStrategyB(Strategy):def do_algorithm(self, data: List) -> List:returnreversed(sorted(data))if__name__=="__main__":# 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. context = Context(ConcreteStrategyA())print("Client: Strategy is set to normal sorting.") context.do_some_business_logic()print()print("Client: Strategy is set to reverse sorting.") context.strategy = ConcreteStrategyB() context.do_some_business_logic()
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
Template
Have a template parent class, where children can override some methods.
from abc import ABC, abstractmethodclass AbstractClass(ABC):""" The Abstract Class defines a template method that contains a skeleton of some algorithm, composed of calls to (usually) abstract primitive operations. Concrete subclasses should implement these operations, but leave the template method itself intact. """def template_method(self) ->None: # KEY POINT""" The template method defines the skeleton of an algorithm. """self.base_operation1()self.required_operations1()self.base_operation2()self.hook1()self.required_operations2()self.base_operation3()self.hook2()# These operations already have implementations.def base_operation1(self) ->None:print("AbstractClass says: I am doing the bulk of the work")def base_operation2(self) ->None:print("AbstractClass says: But I let subclasses override some operations")def base_operation3(self) ->None:print("AbstractClass says: But I am doing the bulk of the work anyway")# These operations have to be implemented in subclasses.@abstractmethoddef required_operations1(self) ->None:pass@abstractmethoddef required_operations2(self) ->None:pass# These are "hooks." Subclasses may override them, but it's not mandatory# since the hooks already have default (but empty) implementation. Hooks# provide additional extension points in some crucial places of the# algorithm.def hook1(self) ->None:passdef hook2(self) ->None:passclass ConcreteClass1(AbstractClass):""" Concrete classes have to implement all abstract operations of the base class. They can also override some operations with a default implementation. """def required_operations1(self) ->None:print("ConcreteClass1 says: Implemented Operation1")def required_operations2(self) ->None:print("ConcreteClass1 says: Implemented Operation2")class ConcreteClass2(AbstractClass):""" Usually, concrete classes override only a fraction of base class' operations. """def required_operations1(self) ->None:print("ConcreteClass2 says: Implemented Operation1")def required_operations2(self) ->None:print("ConcreteClass2 says: Implemented Operation2")def hook1(self) ->None:print("ConcreteClass2 says: Overridden Hook1")def client_code(abstract_class: AbstractClass) ->None:""" The client code calls the template method to execute the algorithm. Client code does not have to know the concrete class of an object it works with, as long as it works with objects through the interface of their base class. """# ... abstract_class.template_method()# ...if__name__=="__main__":print("Same client code can work with different subclasses:") client_code(ConcreteClass1())print("")print("Same client code can work with different subclasses:") client_code(ConcreteClass2())
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway
Visitor
Visitor allows to add new operations to existing classes without modifying them.
from __future__ import annotationsfrom abc import ABC, abstractmethodfrom typing import Listclass Component(ABC):""" The Component interface declares an `accept` method that should take the base visitor interface as an argument. """@abstractmethoddef accept(self, visitor: Visitor) ->None:passclass ConcreteComponentA(Component):""" Each Concrete Component must implement the `accept` method in such a way that it calls the visitor's method corresponding to the component's class. """def accept(self, visitor: Visitor) ->None:""" Note that we're calling `visitConcreteComponentA`, which matches the current class name. This way we let the visitor know the class of the component it works with. """ visitor.visit_concrete_component_a(self)def exclusive_method_of_concrete_component_a(self) ->str:""" Concrete Components may have special methods that don't exist in their base class or interface. The Visitor is still able to use these methods since it's aware of the component's concrete class. """return"A"class ConcreteComponentB(Component):""" Same here: visitConcreteComponentB => ConcreteComponentB """def accept(self, visitor: Visitor): visitor.visit_concrete_component_b(self)def special_method_of_concrete_component_b(self) ->str:return"B"class Visitor(ABC):""" The Visitor Interface declares a set of visiting methods that correspond to component classes. The signature of a visiting method allows the visitor to identify the exact class of the component that it's dealing with. # KEY POINT """@abstractmethoddef visit_concrete_component_a(self, element: ConcreteComponentA) ->None:pass@abstractmethoddef visit_concrete_component_b(self, element: ConcreteComponentB) ->None:pass"""Concrete Visitors implement several versions of the same algorithm, which canwork with all concrete component classes.You can experience the biggest benefit of the Visitor pattern when using it witha complex object structure, such as a Composite tree. In this case, it might behelpful to store some intermediate state of the algorithm while executingvisitor's methods over various objects of the structure."""class ConcreteVisitor1(Visitor):def visit_concrete_component_a(self, element) ->None:print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor1")def visit_concrete_component_b(self, element) ->None:print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor1")class ConcreteVisitor2(Visitor):def visit_concrete_component_a(self, element) ->None:print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor2")def visit_concrete_component_b(self, element) ->None:print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor2")def client_code(components: List[Component], visitor: Visitor) ->None:""" The client code can run visitor operations over any set of elements without figuring out their concrete classes. The accept operation directs a call to the appropriate operation in the visitor object. """# ...for component in components: component.accept(visitor)# ...if__name__=="__main__": components = [ConcreteComponentA(), ConcreteComponentB()]print("The client code works with all visitors via the base Visitor interface:") visitor1 = ConcreteVisitor1() client_code(components, visitor1)print("It allows the same client code to work with different types of visitors:") visitor2 = ConcreteVisitor2() client_code(components, visitor2)
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1
It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2