Посетитель (Visitor)
Поведенческий паттерн проектирвоания, который описывает операцию, выполняемую с каждым объектом из некоторой структуры.
Паттерн посетитель позволяет определить новую операцию, не изменяя классы этих объектов.
Посетитель — это Команда
Реализация паттерна проектирования Посетитель на python
# coding: utf-8 class FruitVisitor(object): """Посетитель""" def draw(self, fruit): methods = { Apple: self.draw_apple, Pear: self.draw_pear, } draw = methods.get(type(fruit), self.draw_unknown) draw(fruit) def draw_apple(self, fruit): print 'Яблоко' def draw_pear(self, fruit): print 'Груша' def draw_unknown(self, fruit): print 'Фрукт' class Fruit(object): """Фрукты""" def draw(self, visitor): visitor.draw(self) class Apple(Fruit): """Яблоко""" class Pear(Fruit): """Груша""" class Banana(Fruit): """Банан""" visitor = FruitVisitor() apple = Apple() apple.draw(visitor) # Яблоко pear = Pear() pear.draw(visitor) # Груша banana = Banana() banana.draw(visitor) # Фрукт
Шаблон visitor чрезвычайно полезен при работе с определенными видами информации, такими как абстрактные синтаксические деревья. Можно назвать это, версией «для бедных» языков, которые изначально не поддерживают работу с типами сумм . К сожалению, там используется перегрузка функций, чего не хватает в таких языках, как Python.
Это сообщение в блоге Криса Лэмба представляет собой умный обходной путь, но не дает фактической реализации для соответствующих декораторов. Идея выглядит следующим образом:
class Lion: pass
class Tiger: pass
class Bear: pass
class ZooVisitor:
@visitor(Lion)
def visit(self, animal):
return «Львы»
@visitor(Tiger)
def visit(self, animal):
return «Тигры»
@visitor(Bear)
def visit(self, animal):
return «и медведи, о боже!»
animals = [Lion(), Tiger(), Bear()]
visitor = ZooVisitor()
print(‘, ‘.join(visitor.visit(animal) for animal in animals))
# Печать «Львы, Тигры, и медведи, о боже!»
Это выглядит слегка подозрительно (поскольку, мы определили три конфликтующих метода в одном классе), но можно постараться написать @visitor таким образом, чтобы он работал:
# Сначала работает пара помощников
def _qualname(obj):
«»»Получить полное имя объекта (включая модуль).»»»
return obj.__module__ + ‘.’ + obj.__qualname__
def _declaring_class(obj):
«»»Получить имя класса, который объявил объект.»»»
name = _qualname(obj)
return name[:name.rfind(‘.’)]
# Сохраняет фактические методы посетителей(visitor)
_methods = {}
# Делегирование реализации посетителя
def _visitor_impl(self, arg):
«»»Фактическая реализация метода посетителя.»»»
method = _methods[(_qualname(type(self)), type(arg))]
return method(self, arg)
# Фактический @visitor декоратор
def visitor(arg_type):
«»»Декоратор, создающий метод посетителя.»»»
def decorator(fn):
declaring_class = _declaring_class(fn)
_methods[(declaring_class, arg_type)] = fn
# Заменит все декорированные методы на _visitor_impl
return _visitor_impl
return decorator
Хитрость здесь в том, что декоратор заменяет все методы visit на _visitor_impl (переопределение существующего метода в Python нормально). Но прежде чем это сделать, он сохраняет исходный метод в словаре, _methods, с ключом посетителя класса и желаемого типа аргумента. Затем при вызове метода visit _visitor_impl ищет соответствующую реализацию и вызывает ее на основе типа аргумента.
Работает для python>=3.3