Design Pattern: Visitor Pattern

Summary

Pattern NameVisitor Pattern
Pattern TypeBehavioral Pattern
ScopeObject
TaglineMove calculation/operation from a group of Classes
to a separate (visitor) class
Use cases1. When we want to move the operation/calculation part
from all the element classes to a completely separate class
2. When we want to change operation/calculation
without changing the classes
Related PatternsComposite
Interpreter
Difficulty LevelMedium
Implementations

Definition

Visitor pattern moves the operation or calculation part from the element/subject classes to a completely separate class. That separate class is called the visitor.

As the operation/calculation is moved to the visitor class, so if we want to change the operation then we can just change the visitor class. No change in the subject classes is required in that case.

Use Cases

Here are cases where we can use the Visitor pattern-

  • When we want to keep the calculation part separate from the item class and also want to keep related operations together.
  • When the object instruction is used by multiple subsystems, and calculation/operation logic/steps of those subsystems are different.

Implementation

Visitor pattern has 5 elements.

  1. Element Interface: interface for the element classes. Should declare a method for accepting the visitor, usually named ‘accept‘.
  2. Concrete Elements: there are the classes from which we want to remove the operation part.
  3. Visitor Interface: an interface for the visitor class. It has the declaration of visit functions, one for each element class.
  4. Concrete Visitor: performs the operation or calculation in the visitor methods defined for each element class.
  5. Client: client needs to call the accept method of the element class, which in turn calls the visit method of the visitor, and performs the calculation.

Follow the steps below to implement Visitor pattern:

  1. Create an interface for element classes and declare an accept method. This accept method will take a param of visitor.
  2. Implement the interface for each element classes. In the accept method call the visit method of the visitor and pass the current class object to that.
  3. Create an interface for the visitor, and declare a visit method for each element class.
  4. Create the visitor class. In the visit class for each element perform the desired operation or calculation.
  5. In the client call the accept method of the element class and pass the visitor to it. That will perform the operation and will return the result.

Examples

Example #1: Hosting Cost Calculator

Let’s consider an example of a hosting service.

Service Interface and implementations

// Service Interface (Element/Subject Interface)

interface Service

    double accept(hostingCalculatorVisitor: HostingCalculatorVisitor)

end interface


// Compute Service

class ComputeService implements Service

    const price: double = 10.50
    var quantity: int

    constructor(quantityParam: int)
        quantity = quantityParam
    end constructor

    method getPrice(): double 
        return price
    end method

    method getQuantity(): int
        return quantity
    end method

    method accept(hostingCalculatorVisitor: HostingCalculatorVisitor): double
        return hostingCalculatorVisitor.visit(this)
    end method

end class


// Database Service

class DatabaseService implements Service

    const price = 100.00
    const backPrice = 30.00

    var quantity: int
    var backupEnabled: boolean

    constructor(quantityParam: int, backupEnabledParam: boolean)
        quantity = quantityParam
        backupEnabled = backupEnabledParam
    end constructor

    method getPrice(): double
        return price
    end method

    method getQuantity(): int
        return quantity
    end method

    method getBackPrice(): double
        return backPrice
    end method

    method isBackupEnabled(): boolean
        return backupEnabled
    end method

    method accept(hostingCalculatorVisitor: HostingCalculatorVisitor): double
        return hostingCalculatorVisitor.visit(this)
    end method

end class



// File Storage Service

class FileStorageService implements Service

    const pricePerGB: double = 1.70
    var quantity: int

    constructor(quantityParam: int)
        quantity = quantityParam
    end constructor

    method getPricePerGB(): double 
        return pricePerGB
    end method

    method getQuantity(): int
        return quantity
    end method

    method accept(hostingCalculatorVisitor: HostingCalculatorVisitor): double
        return hostingCalculatorVisitor.visit(this)
    end method

end class


// Serverless Service

class ServerlessService implements Service

    const hourlyPrice: double = 0.32

    var totalHours: int

    constructor(totalHoursParam: int)
        totalHours = totalHoursParam
    end constructor

    method getHourlyPrice(): double
        return hourlyPrice
    end method

    method getTotalHours(): int
        return totalHours
    end method

    method accept(hostingCalculatorVisitor: HostingCalculatorVisitor): double
        return hostingCalculatorVisitor.visit(this)
    end method

end class

// Container Service

class ContainerService implements Service

    const price: double = 5.60
    var quantity: int

    constructor(quantityParam: int)
        this.quantity = quantityParam
    end constructor

    method getPrice(): double
        return price
    end method

    method getQuantity(): int
        return quantity
    end method

    method accept(hostingCalculatorVisitor: HostingCalculatorVisitor): double
        return hostingCalculatorVisitor.visit(this)
    end method

end class

Visitor Interface and Implementation

// Hosting Calculator Visitor Interface

interface HostingCalculatorVisitor

    visit(computeService: ComputeService): double

    visit(containerService: ContainerService): double

    visit(databaseService: DatabaseService): double

    visit(fileStorageService: FileStorageService): double

    visit(serverlessService: ServerlessService): double

end interface


// Hosting Calculator Visitor Implementation

class HostingCalculatorVisitorImpl implements HostingCalculatorVisitor

    method visit(computeService: ComputeService): double
        return computeService.getPrice() * computeService.getQuantity()
    end method

    method visit(containerService: ContainerService): double
        return containerService.getPrice() * containerService.getQuantity()
    end method

    method visit(databaseService: DatabaseService): double
        double serviceCost = databaseService.getPrice() * databaseService.getQuantity()
        double backupCost = 0

        if (databaseService.isBackupEnabled())
            backupCost = databaseService.getBackPrice() * databaseService.getQuantity()
        end if

        return serviceCost + backupCost
    end method

    method visit(fileStorageService: FileStorageService): double
        return fileStorageService.getPricePerGB() * fileStorageService.getQuantity()
    end method

    method visit(serverlessService: ServerlessService): double
        return serverlessService.getHourlyPrice() * serverlessService.getHourlyPrice()
    end method

end class

Demo

var usedServices = new Service[] {
        new ComputeService(3),
        new DatabaseService(3, true),
        new FileStorageService(120),
        new ServerlessService(720),
        new ContainerService(2),
}

var hostingCalculatorVisitorImpl: HostingCalculatorVisitorImpl = new HostingCalculatorVisitorImpl()

var totalCost = 0

for (service in services) {
    totalCost += service.accept(hostingCalculatorVisitorImpl)
}

output "Total cost of hosting is: " + totalCost

Output

Total cost of hosting is: 636.8024

Code Implementations

Use the following links to check Visitor pattern implementation in specific programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.