Python design patterns #4: visitor

· Design Patterns, Python
Authors

Another favorite. Much simpler than the decorator, but sleeker no less:)

Who left the door open?
What if we could let someone operate over our object, whoever that someone was, whatever our object looks like and for any possible reason?
Intuitively a door left open comes to mind. Its not a matter of having the right key, as everyone are accepted, its about being able of doing a job once inside.

Why?
Why not be aware of the visitor and its nature of visit in advance?
In my opinion, It’s much more than dynamic vs static coding, it’s about main vs auxiliary activities of our object.
We would like our main business activities to be crafted nicely into a hard code, but not all possible / future auxiliary ones.

Good for what?
The ever changing validations and testing are a classic example for that. But also coordination tools like resetting and syncing objects.

So even with time, our object still looks handsome in its original design, allowing different visitors to handle new auxiliary activities.

The door:
Our “host” object, “accepting” the visitor by submitting itself to the visitor.

class Host(object):

    def accept(self, visitor):
        visitor.visit(self)

And the visitor:
Each visitor implements its own visit.

class Visitor(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def visit(self, obj):
        pass

Note the extra flexibility and de-coupling here, making it in my opinion the most dynamic pattern. For anyone willing to accept it, we allow our visitor to perform any visit action. (Which by itself can be selected dynamically from a set of visiting methods a visitor owns. My preference, though, is 1 visit action per visitor)

This is sometimes referred to as a “double dispatch“.
Combining that with a scripting language is perfect, since no reflection is needed.

Visiting..

#A host
class MyHost(Host):

    def __init__(self):
        self.x = 1
        self.y = 10


#A visitor adding 1 to our counters
class PlusOneVisitor(Visitor):

    def visit(self, obj):
        print('{0} is visiting {1}'.format(
            self.__class__.__name__, obj.__class__.__name__))
        for member in obj.__dict__:
            if type(obj.__dict__[member]) is int:
                print('changing {0} from {1} to {2}'.format(
                    member, obj.__dict__[member], obj.__dict__[member] + 1))
                obj.__dict__[member] = obj.__dict__[member] + 1


#A visitor resetting our counters
class ResetVisitor(Visitor):

    def visit(self, obj):
        print('{0} is visiting {1}'.format(
            self.__class__.__name__, obj.__class__.__name__))
        for member in obj.__dict__:
            if type(obj.__dict__[member]) is int:
                print('setting {0} to 0'.format(member))
                obj.__dict__[member] = 0


#A visitor iterating and printing our counters
class PrintMembersVisitor(Visitor):

    def visit(self, obj):
        print('{0} is visiting {1}'.format(
            self.__class__.__name__, obj.__class__.__name__))
        for member in obj.__dict__:
            print('{0} = {1}'.format(member, obj.__dict__[member]))


#Test drive
if __name__ == '__main__':
    host = MyHost()
    host.accept(PlusOneVisitor())
    print_visitor = PrintMembersVisitor()
    host.accept(print_visitor)
    host.accept(ResetVisitor())
    host.accept(print_visitor)


#Output
PlusOneVisitor is visiting MyHost
changing y from 10 to 11
changing x from 1 to 2

PrintMembersVisitor is visiting MyHost
y = 11
x = 2

ResetVisitor is visiting MyHost
setting y to 0
setting x to 0

PrintMembersVisitor is visiting MyHost
y = 0
x = 0

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: