×

Visitor Design Pattern with Python

Visitor Design Patterns Python Feature

Visitor Design Pattern is used where we need to add an additional functionality to an entire class hierarchy, without making any changes to the hierarchy. Or, the Visitor pattern is used when we have to perform a new operation on a group of similar kinds of Objects, without changing the existing objects. 

Visitor Pattern is classified under Behavioural Design Patterns as it provides a good method to handle communication between objects.

The Visitor pattern allows the application of the new operation without changing the class of any of the objects in the collection.

Visitor Pattern suggests defining a separate interface (visitor) whose object implements the operation to be performed on elements of an object structure(i.e. hierarchy). For every additional operation, a new Visitor class is created.

Clients(i.e. other classes) traverse the object structure(i.e hierarchy) and call a dispatching operation to accept (visitor) on an element — that “dispatches” (delegates) the request to the “accepted visitor object”. The visitor object then performs the operation on the element (“visits the element”).

Components Of Visitor Pattern

  • Visitor: An abstract class used to declare the visit operations for all the types of visitable classes.
  • ConcreteVisitor: For each type of visitor all the visit methods, declared in abstract visitor, are implemented in ConcreteVisitor class.
  • Visitable: An abstract class that is used to declare accept operation. The Visitable class provides the access to the object(s) to be “visited” by the visitor class object.
  • ConcreteVisitable: Classes that inherit the Visitable class and defines the accept operation. The visitor-object is passed to ConcereteVisitable- object(s) using the accept operation.
  • ObjectStructure: Class that contains all the objects that can be visited. ObjectStructure class also has a mechanism to iterate through all the objects in the object hierarchy.

Example

Consider, we are working on an e-commerce website, where we can add different types of items in a shopping cart, now want to add a checkout button that will calculate the total amount.

Since each item will be a separate object. Here we can use visitor design patterns to apply the calculate method for getting the total amount.

  • Abstract visitable class — Item, having declaration of accept method.
class Item():
    """Visitable class"""
    @abstractmethod
    def accept(self):
        pass
  • Subclasses of Item abstract class– Shirt and Book, having get_price(), get_size(), and implementaion of accept() method.
class Shirt(Item):
    def __init__(self, price, size):
        self.price = price
        self.size = size

    def get_price(self):
        return self.price

    def get_size(self):
        return self.size

    def accept(self, visitor):
        return visitor.visit(self)
class Book(Item):
    def __init__(self, cost, genre):
        self.price = cost
        self.genre = genre

    def get_price(self):
        return self.price

    def get_genre(self):
        return self.genre

    def accept(self, visitor):
        return visitor.visit(self)
  • The abstract Visitor class. Visitor class has declaration of visit() method.
class Visitor():
    """Abstract Vistor Class"""
    @abstractmethod
    def visit(self, item):
        pass
  • CartVisitor() class, serving as ConcereteVisitor and ObjectStructure class. And, having implementation of Visit() method, method that facilitates traversal the entire class hierarchy.
class CartVisitor(Visitor):
    def visit(self, item):
        if isinstance(item, Book):
            cost = item.get_price()
            print("Book Genre: {}, cost = ${}".format(item.get_genre(), cost))
            return cost
        elif isinstance(item, Shirt):
            cost = item.get_price()
            print("Shirt, size{} cost = ${}".format(item.get_size(), cost))
            return cost
  • calculate_price(), class to define the Operation, that has to be implemented on whole class hierarchy.
def calculate_price(items):
    visitor = CartVisitor()
    sum = 0
    for item in items:
        sum = sum + item.accept(visitor)

    return sum
  • The driver code
if __name__ == '__main__':
    items = [
        Shirt(10, "XL"),
        Shirt(15, "XXL"),
        Book(20, "Fiction"),
        Book(100, "Self Help"),        
    ]

    total = calculate_price(items)
    print("Total Cost = ${}".format(total))
Output:
Shirt, size: XL, cost: $10.
Shirt, size: XXL, cost: $15.
Book, Genre: Fiction, cost: $20.
Book, Genre: Self Help, cost: $100.
Total Cost: $145.

Clearly, Visitor Pattern makes it easy to apply an operation to the whole class hierarchy.

Visitor Pattern helps to uphold Open/Closed Principle — Addition of new functionality without altering the existing code. Since each new operation requires a separate class, hence Visitor Pattern also upholds the Single Responsibility Principle.