
Посетитель на Python
Посетитель — это поведенческий паттерн, который позволяет добавить новую операцию для целой иерархии классов, не изменяя код этих классов.
Подробней о том, почему Посетитель нельзя заменить простой перегрузкой методов читайте в статье Посетитель и Double Dispatch.
Сложность:
Популярность:
Применимость: Посетитель нечасто встречается в Python-коде из-за своей сложности и нюансов реализазации.
Концептуальный пример
Этот пример показывает структуру паттерна Посетитель, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
main.py: Пример структуры паттерна
from __future__ import annotations from abc import ABC, abstractmethod from typing import List class Component(ABC): """ Интерфейс Компонента объявляет метод accept, который в качестве аргумента может получать любой объект, реализующий интерфейс посетителя. """ @abstractmethod def accept(self, visitor: Visitor) -> None: pass class ConcreteComponentA(Component): """ Каждый Конкретный Компонент должен реализовать метод accept таким образом, чтобы он вызывал метод посетителя, соответствующий классу компонента. """ def accept(self, visitor: Visitor) -> None: """ Обратите внимание, мы вызываем visitConcreteComponentA, что соответствует названию текущего класса. Таким образом мы позволяем посетителю узнать, с каким классом компонента он работает. """ visitor.visit_concrete_component_a(self) def exclusive_method_of_concrete_component_a(self) -> str: """ Конкретные Компоненты могут иметь особые методы, не объявленные в их базовом классе или интерфейсе. Посетитель всё же может использовать эти методы, поскольку он знает о конкретном классе компонента. """ return "A" class ConcreteComponentB(Component): """ То же самое здесь: 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): """ Интерфейс Посетителя объявляет набор методов посещения, соответствующих классам компонентов. Сигнатура метода посещения позволяет посетителю определить конкретный класс компонента, с которым он имеет дело. """ @abstractmethod def visit_concrete_component_a(self, element: ConcreteComponentA) -> None: pass @abstractmethod def visit_concrete_component_b(self, element: ConcreteComponentB) -> None: pass """ Конкретные Посетители реализуют несколько версий одного и того же алгоритма, которые могут работать со всеми классами конкретных компонентов. Максимальную выгоду от паттерна Посетитель вы почувствуете, используя его со сложной структурой объектов, такой как дерево Компоновщика. В этом случае было бы полезно хранить некоторое промежуточное состояние алгоритма при выполнении методов посетителя над различными объектами структуры. """ 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: """ Клиентский код может выполнять операции посетителя над любым набором элементов, не выясняя их конкретных классов. Операция принятия направляет вызов к соответствующей операции в объекте посетителя. """ # ... 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)
Output.txt: Результат выполнения
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