The Visitor Design Pattern Detailed Illustration with an Office Furniture System

The Visitor Design Pattern is a behavioral design pattern for specifically adding further operations to objects without modifying their structure. Just as the name sounds, a visitor holds the functionality you want add to an object. For instance, you call an electrician to you home to renovate an electrical issue in your bedroom. This guy already knows every thing related to electrical home renovation. You simply just need to set an appointment with him, he comes and does his operation in your bedroom. This design pattern promotes the separation of algorithms from the objects on which they operate. It makes it easier to add new operations to existing object structures without altering those structures.
To illustrate how the Visitor pattern works, let’s apply it to an office furniture online system.
Office Furniture Online System: Use Case
Imagine you have an online store that sells various types of office furniture, such as desks, chairs, and filing cabinets. Each of these furniture types is represented by a class in your system, and each class has different properties and methods. As the business grows, the system requires additional functionalities, such as calculating shipping costs, applying discounts, or generating product descriptions.
Instead of modifying the existing furniture classes each time a new functionality is required, you can use the Visitor Design Pattern to add these operations without altering the furniture classes themselves.

Key Components of the Visitor Pattern
Visitor Interface:
- The
Visitor
interface declares visit methods for each type of element in the object structure. These methods define what should be done when visiting an element. - Example:
FurnitureVisitor
with methodsvisitDesk(Desk $desk)
,visitChair(Chair $chair)
, andvisitCabinet(Cabinet $cabinet)
.
Concrete Visitor:
- A
ConcreteVisitor
implements the operations defined in theVisitor
interface. It contains the logic that must be executed when visiting each element. - Example:
ShippingCostVisitor
calculates shipping costs,DiscountVisitor
applies discounts, andDescriptionVisitor
generates product descriptions.

Element Interface:
- The
Element
interface declares anaccept
method that takes a visitor as an argument. - Example:
FurnitureItem
with anaccept(FurnitureVisitor $visitor)
method.
Concrete Elements:
ConcreteElements
implement theElement
interface and define how they accept a visitor by invoking the visitor’s corresponding method.- Example:
Desk
,Chair
, andCabinet
classes that implementFurnitureItem
and have anaccept
method.

Object Structure:
- The
ObjectStructure
represents a collection of elements that can be visited. It’s responsible for providing access to the elements and may allow the visitor to traverse through them. - Example: A
FurnitureCollection
class containing a list ofFurnitureItem
objects.
Implementation Example

Let’s walk through an example of how the Visitor pattern might be implemented in the office furniture online system.
1. Visitor Interface:
interface FurnitureVisitor {
public function visitDesk(Desk $desk);
public function visitChair(Chair $chair);
public function visitCabinet(Cabinet $cabinet);
}
2. Concrete Visitor:
class ShippingCostVisitor implements FurnitureVisitor {
public function visitDesk(Desk $desk) {
// Calculate shipping cost based on desk dimensions and weight
return $desk->getWeight() * 0.5 + $desk->getDimensions();
}
public function visitChair(Chair $chair) {
// Calculate shipping cost based on chair weight and size
return $chair->getWeight() * 0.3 + $chair->getSize();
}
public function visitCabinet(Cabinet $cabinet) {
// Calculate shipping cost based on cabinet dimensions and weight
return $cabinet->getWeight() * 0.7 + $cabinet->getDimensions();
}
}
3. Element Interface:
interface FurnitureItem {
public function accept(FurnitureVisitor $visitor);
}
4. Concrete Elements:
class Desk implements FurnitureItem {
private $weight;
private $dimensions;
public function __construct($weight, $dimensions) {
$this->weight = $weight;
$this->dimensions = $dimensions;
}
public function getWeight() {
return $this->weight;
}
public function getDimensions() {
return $this->dimensions;
}
public function accept(FurnitureVisitor $visitor) {
return $visitor->visitDesk($this);
}
}
class Chair implements FurnitureItem {
private $weight;
private $size;
public function __construct($weight, $size) {
$this->weight = $weight;
$this->size = $size;
}
public function getWeight() {
return $this->weight;
}
public function getSize() {
return $this->size;
}
public function accept(FurnitureVisitor $visitor) {
return $visitor->visitChair($this);
}
}
class Cabinet implements FurnitureItem {
private $weight;
private $dimensions;
public function __construct($weight, $dimensions) {
$this->weight = $weight;
$this->dimensions = $dimensions;
}
public function getWeight() {
return $this->weight;
}
public function getDimensions() {
return $this->dimensions;
}
public function accept(FurnitureVisitor $visitor) {
return $visitor->visitCabinet($this);
}
}
5. Object Structure:
class FurnitureCollection {
private $items = [];
public function addItem(FurnitureItem $item) {
$items[] = $item;
}
public function accept(FurnitureVisitor $visitor) {
foreach ($this->items as $item) {
$item->accept($visitor);
}
}
}
Usage Example:
// Create furniture items
$desk = new Desk(50, 120);
$chair = new Chair(20, 80);
$cabinet = new Cabinet(70, 150);
// Add items to collection
$furnitureCollection = new FurnitureCollection();
$furnitureCollection->addItem($desk);
$furnitureCollection->addItem($chair);
$furnitureCollection->addItem($cabinet);
// Create a visitor to calculate shipping costs
$shippingCostVisitor = new ShippingCostVisitor();
// Apply the visitor to the collection
$furnitureCollection->accept($shippingCostVisitor);
Advantages of Using the Visitor Pattern
Separation of Concerns:
The Visitor pattern allows you to separate operations from the objects they operate on. This keeps your classes cleaner and more focused on their primary responsibilities.
Extensibility:
You can easily add new operations to your object structure without modifying the objects themselves. Adding a new visitor is straightforward and doesn’t require changes to the existing code.
Open/Closed Principle: — [SOLID PRINCIPLES]
The pattern adheres to the Open/Closed Principle, which states that classes should be open for extension but closed for modification.
The SOLID principles are a set of five design guidelines — Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion . They particularly promote better software design, encouraging maintainable, scalable, and robust code.
Considerations and Drawbacks
Complexity:
The Visitor pattern can introduce additional complexity, especially in systems with many different types of elements. Each new visitor requires implementing visit methods for each element type.
Double Dispatch:
The Visitor pattern relies on double dispatch, meaning it involves two method calls to execute an operation: one on the element to accept the visitor and one on the visitor to perform the operation. This can lead to more complex code structures.
Conclusion
The Visitor Design Pattern is a powerful tool for adding new operations to an existing class hierarchy without altering the classes themselves. In an office furniture online system, it allows you to add features like shipping cost calculations, discount applications, and product descriptions in a modular, maintainable way. This Visitor pattern, will keep your system flexible and extendable while maintaining new features to be added with minimal disruption to existing code.
Read More about Strategy Design Pattern part of Behavioral Design Pattern commonly used