Design patterns: Visitor
The Visitor Pattern is a behavioral pattern that lets you separate an algorithm from the object structure it operates on. This pattern lets you add new operations to a set of objects without modifying their classes. You define a Visitor class that represents an operation to perform on the elements of the structure, and each element of the structure accepts a visitor that carries out the appropriate operation.
Strengths
- Adding new operations without modifying existing classes: The pattern lets you extend the functionality of a class structure without touching its code, separating the algorithm from the structure.
- Single responsibility: It lets you separate operations from the data structure, honoring the Single Responsibility Principle.
- Easy addition of new operations: Operations can be added by creating new visitors, without modifying the element classes.
- Removing duplicate code: It lets you centralize common logic inside the visitors, eliminating code duplication across elements.
- Works with heterogeneous classes: It works well with complex object structures and with classes that don’t share a common hierarchy.
Use case: A company structure report
Imagine a company structure with various roles (Manager, Developer) on which you want to perform different operations (such as calculating bonuses or generating reports). The Visitor pattern lets you perform these operations separately from the role classes themselves.
PHP Code
// Interface for elements that can be visited
interface Employee {
public function accept(EmployeeVisitor $visitor);
}
// Concrete class for the Manager
class Manager implements Employee {
private $name;
private $salary;
public function __construct($name, $salary) {
$this->name = $name;
$this->salary = $salary;
}
public function getName() {
return $this->name;
}
public function getSalary() {
return $this->salary;
}
// Accepts the visitor
public function accept(EmployeeVisitor $visitor) {
$visitor->visitManager($this);
}
}
// Concrete class for the Developer
class Developer implements Employee {
private $name;
private $salary;
public function __construct($name, $salary) {
$this->name = $name;
$this->salary = $salary;
}
public function getName() {
return $this->name;
}
public function getSalary() {
return $this->salary;
}
// Accepts the visitor
public function accept(EmployeeVisitor $visitor) {
$visitor->visitDeveloper($this);
}
}
// Interface for visitors
interface EmployeeVisitor {
public function visitManager(Manager $manager);
public function visitDeveloper(Developer $developer);
}
// Concrete visitor that calculates the bonus
class BonusCalculatorVisitor implements EmployeeVisitor {
public function visitManager(Manager $manager) {
$bonus = $manager->getSalary() * 0.10;
echo "Bonus for Manager {$manager->getName()}: $bonus\n";
}
public function visitDeveloper(Developer $developer) {
$bonus = $developer->getSalary() * 0.05;
echo "Bonus for Developer {$developer->getName()}: $bonus\n";
}
}
// Concrete visitor that generates reports
class ReportGeneratorVisitor implements EmployeeVisitor {
public function visitManager(Manager $manager) {
echo "Generating report for Manager: {$manager->getName()} with salary: {$manager->getSalary()}\n";
}
public function visitDeveloper(Developer $developer) {
echo "Generating report for Developer: {$developer->getName()} with salary: {$developer->getSalary()}\n";
}
}
// Usage example
$employees = [
new Manager("Mario Rossi", 5000),
new Developer("Luca Verdi", 3000)
];
// Create the visitor that calculates bonuses
$bonusVisitor = new BonusCalculatorVisitor();
foreach ($employees as $employee) {
$employee->accept($bonusVisitor);
}
echo "\n";
// Create the visitor that generates reports
$reportVisitor = new ReportGeneratorVisitor();
foreach ($employees as $employee) {
$employee->accept($reportVisitor);
}
Explanation
- Employee interface: Defines the accept() method, which accepts a visitor. Manager and Developer classes: Implement the Employee interface and override the accept() method to accept a visitor. EmployeeVisitor interface: Defines the methods for visiting each type of object (Manager and Developer). BonusCalculatorVisitor and ReportGeneratorVisitor classes: Implement the EmployeeVisitor interface and define the specific operations for each type of object. Using the Visitor pattern: The accept() method lets you apply the visitor to the elements of the structure, performing the specific operations without modifying the objects’ classes.
Conclusion
The Visitor Pattern lets you add new operations to a complex object structure without modifying its classes. It’s useful when you want to keep operation code separate from data classes, ensuring extensibility and flexibility.