Summary
Pattern Name | Visitor Pattern |
Pattern Type | Behavioral Pattern |
Scope | Object |
Tagline | Move calculation/operation from a group of Classes to a separate (visitor) class |
Use cases | 1. 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 Patterns | Composite Interpreter |
Difficulty Level | Medium |
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.
- Element Interface: interface for the element classes. Should declare a method for accepting the visitor, usually named ‘accept‘.
- Concrete Elements: there are the classes from which we want to remove the operation part.
- Visitor Interface: an interface for the visitor class. It has the declaration of visit functions, one for each element class.
- Concrete Visitor: performs the operation or calculation in the visitor methods defined for each element class.
- 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:
- Create an interface for element classes and declare an accept method. This accept method will take a param of visitor.
- 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.
- Create an interface for the visitor, and declare a visit method for each element class.
- Create the visitor class. In the visit class for each element perform the desired operation or calculation.
- 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.