Visitor pattern explained

The visitor pattern could possibly be one of the most difficult patterns to understand due to its double dispatch delegation. Yet it is a fundamental pattern.

I hope that this article is the last one you need to read to understand the visitor pattern.

Visitor pattern as a behavioral design pattern

Behavioral design patterns provide solutions to common problems related to objects communication. The visitor pattern is one of the twenty-three well-known GoF design patterns.

The visitor pattern allows us to create a flexible and reusable object-oriented software by decoupling operations from the object structure.

Visitor pattern usage

The Visitor pattern can be used when we want to add unrelated operations to a set of classes.

Unrelated operations are those operations that do not belong to the class.

Let’s think that we are doing a reporter application. We have the following classes:

class HotelBill {
    numberOfNights(){/* code */}
    pricePerNight(){/* code */}

    // Unrelated to the class
    report(){/* code */}
}

class RestaurantBill {
    priceAppetizers(){/* code */}
    priceFirstDish(){/* code */}
    priceMainCourse(){/* code */}
    priceDessert(){/* code */}
    priceDrinks(){/* code */}

    // Unrelated to the class
    report(){/* code */}
}

Having a report operation on these classes breaks single responsability principle.

HotelBill nor RestaurantBill should not be responsible of reporting our expenses at the end of the month.

Not only that. The well-known GoF design patterns says:

*”distributing all these operations across the various node classes leads to a system that’s hard to understand, maintain, and change.”*

Visitor pattern applied

The Visitor pattern creates a class for taking care of unrelated operations applied to a set of classes.

Instead of adding an operation to every single class in the object structure or using subclasses, we gather that operation into its own class.

The terminology used for this class includes the Visitor suffix.

Once we have the operation isolated in its own class, we create as many visit methods as needed. Each of these methods will receive an object to apply the operation.

// Extract all report operations to apply to Bills to its own class.
class ReportGeneratorVisitor {
    visitHotelBill(){ /* code */ }
    visitRestaurantBill(){ /* code */ }
}

To invoke these methods, our classes must accept the visitor. They delegate the operation to be done to the received visitor.

class IBill {
    accept(reportGeneratorVisitor) {
        throw Error('missing implementation')
    }
}

class HotelBill extends IBill {
    numberOfNights(){/* code */}
    pricePerNight(){/* code */}

    accept(reportGeneratorVisitor) {
        reportGeneratorVisitor.visitHotelBill(this)
    }
}

class RestaurantBill extends IBill {
    priceAppetizersDish(){/* code */}
    priceFirstDish(){/* code */}
    priceMainCourseDish(){/* code */}
    priceDessert(){/* code */}
    priceDrinks(){/* code */}

    accept(reportGeneratorVisitor) {
        reportGeneratorVisitor.visitRestaurantBill(this)
    }
}

Our client would look something like this:


class ExpenseReporter {
    constructor(bills){
      this._bills = bills
    }

    report(){
      const reportGeneratorVisitor = new ReportGeneratorVisitor()

      let report = []
      // Iterate over bills accepting the visitor.
      for( bill in this._bills ) {
        //
        // Recall that the visitor has the responsability
        //   of knowing how to do the reporting operation
        //   for every type of bill. 
        //
        report.push(
          bill.accept(reportGeneratorVisitor)
        )
      }

      return report
    }
}

Visitor pattern class diagram

Visitor pattern class diagram in UML

Bear in mind that understanding the design pattern will help you more than memorizing the class diagram.

Wikipedia - Visitor pattern

DZone - Design Patterns - Visitor

[Stackoverflow - Visitor pattern purpose](https://stackoverflow.com/ questions/2604169/visitor-patterns-purpose-with-examples)

Credits

Photo by Todd Trapani on Unsplash