|
| 1 | +""" |
| 2 | +EN: Visitor Design Pattern |
| 3 | +
|
| 4 | +Intent: Represent an operation to be performed over elements of an object |
| 5 | +structure. The Visitor pattern lets you define a new operation without |
| 6 | +changing the classes of the elements on which it operates. |
| 7 | +
|
| 8 | +RU: Паттерн Посетитель |
| 9 | +
|
| 10 | +Назначение: Позволяет добавлять в программу новые операции, не изменяя классы |
| 11 | +объектов, над которыми эти операции могут выполняться. |
| 12 | +""" |
| 13 | + |
| 14 | + |
| 15 | +from __future__ import annotations |
| 16 | +from abc import ABCMeta, abstractmethod |
| 17 | +from typing import List |
| 18 | + |
| 19 | + |
| 20 | +class Component(metaclass=ABCMeta): |
| 21 | + """ |
| 22 | + EN: The Component interface declares an `accept` method that should take the |
| 23 | + base visitor interface as an argument. |
| 24 | +
|
| 25 | + RU: Интерфейс Компонента объявляет метод accept, который в качестве |
| 26 | + аргумента может получать любой объект, реализующий интерфейс посетителя. |
| 27 | + """ |
| 28 | + |
| 29 | + @abstractmethod |
| 30 | + def accept(self, visitor: Visitor) -> None: |
| 31 | + pass |
| 32 | + |
| 33 | + |
| 34 | +class ConcreteComponentA(Component): |
| 35 | + """ |
| 36 | + EN: Each Concrete Component must implement the `accept` method in such a way |
| 37 | + that it calls the visitor's method corresponding to the component's class. |
| 38 | +
|
| 39 | + RU: Каждый Конкретный Компонент должен реализовать метод accept таким |
| 40 | + образом, чтобы он вызывал метод посетителя, соответствующий классу компонента. |
| 41 | + """ |
| 42 | + |
| 43 | + def accept(self, visitor: Visitor) -> None: |
| 44 | + """ |
| 45 | + EN: Note that we're calling `visitConcreteComponentA`, which matches the |
| 46 | + current class name. This way we let the visitor know the class of the |
| 47 | + component it works with. |
| 48 | +
|
| 49 | + RU: Обратите внимание, мы вызываем visitConcreteComponentA, что |
| 50 | + соответствует названию текущего класса. Таким образом мы позволяем |
| 51 | + посетителю узнать, с каким классом компонента он работает. |
| 52 | + """ |
| 53 | + |
| 54 | + visitor.visit_concrete_component_a(self) |
| 55 | + |
| 56 | + def exclusive_method_of_concrete_component_a(self) -> str: |
| 57 | + """ |
| 58 | + EN: Concrete Components may have special methods that don't exist in |
| 59 | + their base class or interface. The Visitor is still able to use these |
| 60 | + methods since it's aware of the component's concrete class. |
| 61 | +
|
| 62 | + RU: Конкретные Компоненты могут иметь особые методы, не объявленные в их |
| 63 | + базовом классе или интерфейсе. Посетитель всё же может использовать эти |
| 64 | + методы, поскольку он знает о конкретном классе компонента. |
| 65 | + """ |
| 66 | + |
| 67 | + return "A" |
| 68 | + |
| 69 | + |
| 70 | +class ConcreteComponentB(Component): |
| 71 | + """ |
| 72 | + EN: Same here: visitConcreteComponentB => ConcreteComponentB |
| 73 | +
|
| 74 | + RU: То же самое здесь: visitConcreteComponentB => ConcreteComponentB |
| 75 | + """ |
| 76 | + |
| 77 | + def accept(self, visitor: Visitor): |
| 78 | + visitor.visit_concrete_component_b(self) |
| 79 | + |
| 80 | + def special_method_of_concrete_component_b(self) -> str: |
| 81 | + return "B" |
| 82 | + |
| 83 | + |
| 84 | +class Visitor(metaclass=ABCMeta): |
| 85 | + """ |
| 86 | + EN: The Visitor Interface declares a set of visiting methods that correspond |
| 87 | + to component classes. The signature of a visiting method allows the visitor |
| 88 | + to identify the exact class of the component that it's dealing with. |
| 89 | +
|
| 90 | + RU: Интерфейс Посетителя объявляет набор методов посещения, соответствующих |
| 91 | + классам компонентов. Сигнатура метода посещения позволяет посетителю |
| 92 | + определить конкретный класс компонента, с которым он имеет дело. |
| 93 | + """ |
| 94 | + |
| 95 | + @abstractmethod |
| 96 | + def visit_concrete_component_a(self, element: ConcreteComponentA) -> None: |
| 97 | + pass |
| 98 | + |
| 99 | + @abstractmethod |
| 100 | + def visit_concrete_component_b(self, element: ConcreteComponentB) -> None: |
| 101 | + pass |
| 102 | + |
| 103 | + |
| 104 | +""" |
| 105 | + EN: Concrete Visitors implement several versions of the same algorithm, which |
| 106 | + can work with all concrete component classes. |
| 107 | + |
| 108 | + You can experience the biggest benefit of the Visitor pattern when using it |
| 109 | + with a complex object structure, such as a Composite tree. In this case, it |
| 110 | + might be helpful to store some intermediate state of the algorithm while |
| 111 | + executing visitor's methods over various objects of the structure. |
| 112 | + |
| 113 | + RU: Конкретные Посетители реализуют несколько версий одного и того же |
| 114 | + алгоритма, которые могут работать со всеми классами конкретных компонентов. |
| 115 | + |
| 116 | + Максимальную выгоду от паттерна Посетитель вы почувствуете, используя его со |
| 117 | + сложной структурой объектов, такой как дерево Компоновщика. В этом случае |
| 118 | + было бы полезно хранить некоторое промежуточное состояние алгоритма при |
| 119 | + выполнении методов посетителя над различными объектами структуры. |
| 120 | +""" |
| 121 | + |
| 122 | + |
| 123 | +class ConcreteVisitor1(Visitor): |
| 124 | + def visit_concrete_component_a(self, element) -> None: |
| 125 | + print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor1") |
| 126 | + |
| 127 | + def visit_concrete_component_b(self, element) -> None: |
| 128 | + print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor1") |
| 129 | + |
| 130 | + |
| 131 | +class ConcreteVisitor2(Visitor): |
| 132 | + def visit_concrete_component_a(self, element) -> None: |
| 133 | + print(f"{element.exclusive_method_of_concrete_component_a()} + ConcreteVisitor2") |
| 134 | + |
| 135 | + def visit_concrete_component_b(self, element) -> None: |
| 136 | + print(f"{element.special_method_of_concrete_component_b()} + ConcreteVisitor2") |
| 137 | + |
| 138 | + |
| 139 | +def client_code(components: List[Component], visitor: Visitor) -> None: |
| 140 | + """ |
| 141 | + EN: The client code can run visitor operations over any set of elements |
| 142 | + without figuring out their concrete classes. The accept operation directs a |
| 143 | + call to the appropriate operation in the visitor object. |
| 144 | +
|
| 145 | + RU: Клиентский код может выполнять операции посетителя над любым набором |
| 146 | + элементов, не выясняя их конкретных классов. Операция принятия направляет |
| 147 | + вызов к соответствующей операции в объекте посетителя. |
| 148 | + """ |
| 149 | + |
| 150 | + # ... |
| 151 | + for component in components: |
| 152 | + component.accept(visitor) |
| 153 | + # ... |
| 154 | + |
| 155 | + |
| 156 | +if __name__ == "__main__": |
| 157 | + components = [ConcreteComponentA(), ConcreteComponentB()] |
| 158 | + |
| 159 | + print("The client code works with all visitors via the base Visitor interface:") |
| 160 | + visitor1 = ConcreteVisitor1() |
| 161 | + client_code(components, visitor1) |
| 162 | + |
| 163 | + print("It allows the same client code to work with different types of visitors:") |
| 164 | + visitor2 = ConcreteVisitor2() |
| 165 | + client_code(components, visitor2) |
0 commit comments