[AST] (http://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E6%A7%8B%E6%96%87%E6%9C%A8) is being investigated [Visitor pattern] (http://ja.wikipedia.org/wiki/Visitor_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3) came out. What made you happy with the Visitor pattern? I didn't know the basics, so I reviewed it: sweat:
Wikipedia has a Java code example, so I rewrote it in Python3. At first, I ported the Japanese code while looking at the example, and if I thought that it was a half-finished sample, the English Visitor pattern ) Has been modified to a more concise code example. It seems better to look at this page in English.
Python doesn't have an interface, so I'm using the @abstractmethod
decorator from the abc module. As an aside, the * abc * module only applies to instances of classes, but * zope.interface * is great for classes, objects, modules, etc.
Quiet talk break. Let's look at the pattern while looking at the code.
3.4
# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod
class ICarElementVisitor(metaclass=ABCMeta):
"""
Interface like in Python
"""
@abstractmethod
def visit_Wheel(self, wheel): pass
@abstractmethod
def visit_Engine(self, engine): pass
@abstractmethod
def visit_Body(self, body): pass
@abstractmethod
def visit_Car(self, car): pass
class ICarElement(metaclass=ABCMeta):
"""
Interface like in Python
"""
@abstractmethod
def accept(self, visitor): pass
class Wheel(ICarElement):
def __init__(self, name):
self.name = name
def accept(self, visitor):
visitor.visit_Wheel(self)
class Engine(ICarElement):
def accept(self, visitor):
visitor.visit_Engine(self)
class Body(ICarElement):
def accept(self, visitor):
visitor.visit_Body(self)
class Car(ICarElement):
def __init__(self):
self.elements = [
Wheel('front left'), Wheel('front right'),
Wheel('back left'), Wheel('back right'),
Body(), Engine(),
]
def accept(self, visitor):
for elem in self.elements:
elem.accept(visitor)
visitor.visit_Car(self)
class PrintVisitor(ICarElementVisitor):
def visit_Wheel(self, wheel):
print('Visiting {} wheel'.format(wheel.name))
def visit_Engine(self, engine):
print('Visiting engine')
def visit_Body(self, body):
print('Visiting body')
def visit_Car(self, car):
print('Visiting car')
class DoVisitor(ICarElementVisitor):
def visit_Wheel(self, wheel):
print('Kicking my {} wheel'.format(wheel.name))
def visit_Engine(self, engine):
print('Starting my engine')
def visit_Body(self, body):
print('Moving my body')
def visit_Car(self, car):
print('Starting my car')
def main():
"""
>>> main()
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
--------------------------------
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car
"""
car = Car()
car.accept(PrintVisitor())
print('-' * 32)
car.accept(DoVisitor())
If you port the Java code as it is, it looks like this, but since it is Python, let's loosen it a little more.
--- visitor.py 2015-02-17 18:43:53.000000000 +0900
+++ visitor-generic.py 2015-02-17 18:46:24.000000000 +0900
@@ -6,16 +6,8 @@
Interface like in Python
"""
@abstractmethod
- def visit_Wheel(self, wheel): pass
-
- @abstractmethod
- def visit_Engine(self, engine): pass
-
- @abstractmethod
- def visit_Body(self, body): pass
-
- @abstractmethod
- def visit_Car(self, car): pass
+ def visit(self, obj):
+ getattr(self, 'visit_' + obj.__class__.__name__)(obj)
class ICarElement(metaclass=ABCMeta):
"""
@@ -29,15 +21,15 @@
self.name = name
def accept(self, visitor):
- visitor.visit_Wheel(self)
+ visitor.visit(self)
class Engine(ICarElement):
def accept(self, visitor):
- visitor.visit_Engine(self)
+ visitor.visit(self)
class Body(ICarElement):
def accept(self, visitor):
- visitor.visit_Body(self)
+ visitor.visit(self)
class Car(ICarElement):
@@ -51,9 +43,12 @@
def accept(self, visitor):
for elem in self.elements:
elem.accept(visitor)
- visitor.visit_Car(self)
+ visitor.visit(self)
class PrintVisitor(ICarElementVisitor):
+ def visit(self, obj):
+ getattr(self, 'visit_' + obj.__class__.__name__)(obj)
+
def visit_Wheel(self, wheel):
print('Visiting {} wheel'.format(wheel.name))
@@ -67,6 +62,9 @@
print('Visiting car')
class DoVisitor(ICarElementVisitor):
+ def visit(self, obj):
+ super().visit(obj)
+
To briefly explain the changes, we use reflection to define a default implementation for something interface-like.
3.4
class ICarElementVisitor(metaclass=ABCMeta):
"""
Interface like in Python
"""
@abstractmethod
def visit(self, obj):
getattr(self, 'visit_' + obj.__class__.__name__)(obj)
Then, the class that receives the visitor can be unified to call visitor.visit (self)
, which is a little cleaner.
3.4
class Engine(ICarElement):
def accept(self, visitor):
visitor.visit(self)
The Visitor subclass may directly define an implementation of visit ()
, such as PrintVisitor
, or it may use the default implementation of an abstract class (interface), such as DoVisitor
.
3.4
class PrintVisitor(ICarElementVisitor):
def visit(self, obj):
getattr(self, 'visit_' + obj.__class__.__name__)(obj)
...
class DoVisitor(ICarElementVisitor):
def visit(self, obj):
super().visit(obj)
...
The Visitor pattern passes the Visitor object to the ʻaccept ()method and calls the
visitor.visit (self) method inside it ([Double Dispatch](http://en.wikipedia.org/wiki/Double_dispatch)). ). This example also calls the ʻelem.accept (visitor)
method of each element object inside car.accept (visitor)
.
One of the benefits is that you can operate across multiple element objects (* traverse *) so that you don't have to change the code or data structure of an existing object when you add a new operation [Separation of]. It leads to concerns (http://en.wikipedia.org/wiki/Separation_of_concerns).
For example, if you think about adding an operation called braking,
+class Brake(ICarElement):
+ def accept(self, visitor):
+ visitor.visit(self)
+
class Car(ICarElement):
def __init__(self):
self.elements = [
Wheel('front left'), Wheel('front right'),
Wheel('back left'), Wheel('back right'),
- Body(), Engine(),
+ Body(), Engine(), Brake(),
]
@@ -55,6 +59,9 @@
def visit_Engine(self, engine):
print('Visiting engine')
+ def visit_Brake(self, engine):
+ print('Visiting brake')
+
That's all you need to do.
In addition, the visitor
object is described as being more powerful than the * polymorphic * method because it can also have states.
In this example, we don't manage the state in particular, but it's easy to record the order in which they were called, or flag them when an operation is performed.
After writing it and re-examining it, [Comparison of visitor pattern and duck typing] According to (http://d.hatena.ne.jp/podhmo/20101127/1290845198), [Expert Python Programming](http://ascii.asciimw.jp/books/books/detail/978-4-04- Was it listed in 868629-7.shtml)? It was many years ago, and I remember attending a book club at that time and talking about it, but I didn't remember the code at all.
Recommended Posts