Design pattern in PHP: Visitor Pattern

The visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures.

What problems can it solve?

When you work with collections of objects, you may need to apply various operations to the structure that involve working with each individual component. Let’s check an example below.

<?php

/**
* unit of a computure
*/
abstract class Unit
{
	/**
	* print unit's name
	*/
	abstract public function getName();

}

/**
* Cpu
*/
class Cpu extends Unit
{
	public function getName()
	{
		return 'i am cpu';
	}
}

/**
* memory
*/
class Memory extends Unit
{
	public function getName()
	{
		return 'i am memory';
	}
}

/**
* keyboard
*/
class Keyboard extends Unit
{
	public function getName()
	{
		return 'i am keyboard';
	}
}

/**
* computer
*/
class Computer
{
	protected $_items = [];

	public function add(Unit $unit)
	{
		$this->_items[] = $unit;
	}

	public function print()
	{
		// print every unit of the computer
		foreach ($this->_items as $item) {
			echo $item->getName();
		}
	}
}

In this case, it looks like everything going well. But when we are asked to add some more operations to computer, such as saving the computer to a database, we have to add save() method in computer class to do that. With operations increase, computer class will become bigger which is not what we want obviously. So what else can we do?

What visitor pattern can do?

The key point of this pattern is loosing coupling between algorithm and object structure. In last case, we should make out some way to separate the operation from the computer.

Implementation

Let’s see an example of visitor pattern in PHP.

Step 1: define a visitor interface

// unit visitor
interface Visitor
{
	public function visitCpu(Cpu $cpu);
	public function visitMemory(Memory $memory);
	public function visitKeyboard(Keyboard $keyboard);
}

Step 2: create print visitor

/**
* printVisitor
*/
class PrintVisitor implements Visitor
{
	public function visitCpu(Cpu $cpu)
	{
		echo "hello, " . $cpu->getName() . "\n";
	}

	public function visitMemory(Memory $memory)
	{
		echo "hello, " . $memory->getName() . "\n";
	}

	public function visitKeyboard(Keyboard $keyboard)
	{
		echo "hello, " . $keyboard->getName() . "\n";
	}
}

Step 3: recreate unit class

/**
* unit of a computure
*/
abstract class Unit
{
	/**
	 * print unit's name
	 */
	abstract public function getName();
	
	/**
	 * accept the visitor instance and call back visit method
	 * a key method
	 * @param  Visitor $visitor
	 * @return void
	 */
	public function accept(Visitor $visitor)
	{
		$method = visit . get_class($this);
		if (method_exists($visitor, $method)) {
			$visitor->$method($this);
		}
	}
}

Step 4: modify computer class

/**
* computer
*/
class Computer
{
	protected $_items = [];

	public function add(Unit $unit)
	{
		$this->_items[] = $unit;
	}

	public function print()
	{
		// print every unit of the computer
		foreach ($this->_items as $item) {
			echo $item->getName();
		}
	}
}

Step 5: create some instances

$computer = new Computer();
$printVisitor = new PrintVisitor();
$computer->add(new Cpu);
$computer->add(new Memory);
$computer->add(new Keyboard);

$computer->accept($printVisitor);

Step 6: run the script and check the result

$  designpatterns php visitor.php
hello, i am cpu
hello, i am memory
hello, i am keyboard

After modification, we can add more operations without changing computer class and unit class.

Pros of visitor pattern

  1. Add more operation without changing current structure.
  2. Excellent scalability.
  3. Excellent flexibility.

Cons of visitor pattern

  1. The unit class are spread out in all the Visitor classes. Therefore, the Unit’s logic lives in many classes.
  2. Break the principle of dependency inversion, relies on concrete classes, and does not rely on abstractions.

RSS