Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Patterns: Visitor

DZone's Guide to

Practical PHP Patterns: Visitor

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

The Visitor pattern is a mechanism to allow external objects to access an object structure without resulting in a mutual dependency. Typically this pattern is used when dealing with a stable data structure, but a set of rapidly changing operations. The pattern sets up a class structure where it is easier to change the operations by adding new Visitors.

Every Visitor implementation has methods for visiting every concrete element type, and provide an operation. Thus what could have been a method on a class (the operation) becomes a first-class object.

Participants

  • Element: the basic interface of the data structure, which contains only the method acceptVisitor(Visitor);
  • ConcreteElement: the various concrete classes of the data structure. May be more than one.
  • Visitor: basic interface for operations. It contains visit() method for all the ConcreteElement classes.
  • ConcreteVisitor: an operation. Its various implementations for the particular ConcreteElements are contained in its visit() methods.

Implementation

Every ConcreteElement implements the Element interface by providing an acceptVisitor(), which simply calls the right callback, one of the visit() methods of the passed ConcreteVisitor. When an operation is added or changed, only the related ConcreteVisitor is affected.

If we simply use ordinary methods instead of Visitors, every new operation defined by Element must be implemented in all ConcreteElement classes, either manually or through an abstract class with a default implementation where applicable. In some use cases, this may not even be possible, for example when you want to plug in different operations (Visitor objects) without modifying the existing codebase (it's the case with extensions and plugins.)

Although the Visitor is an external object, the Api of this pattern is still readable as a clear extension point, acceptVisitor(), is provided.

Procedural bits

Visitor implementations are sometimes a return to procedural programming, where a data structure is separated from its behavior. In fact, the encapsulation provided by the Element classes can't be complete for the ConcreteVisitors to work. There are several differences though between being procedural and implementing a Visitor pattern.

First, the Visitors are not simple functions but full-features objects: they may have collaborators which can be wired at runtime, or being the Facade of any object graph. Furthermore, a Visitor can maintain state, something which a classical function cannot without hiding it under the carpet (for example via static internal variables).

Second, when plain data structures are used in object-oriented programming, they must be very dumb classes and present nearly no logic, since they can't usually be stubbed out. I have no problem for example in creating and passing around a Collection or array-like class like I would do with a string or integer, as long as they do not contain business logic. The Visitor pattern helps in keeping operations separate from data structures - in order to continue using the latter freely, without resorting to factories or complex injection processes.

Advantages

Mainly, there are no dependencies from the elements towards the Visitors.
Also the addition of operations (other visitors) do not require changes to the elements, in accordance with the Open/Closed Principle.

The Visitor pattern is helpful in concentrating operation-specific code, instead of spreading it all over the ConcreteElements. Operation-specific data structures reside in the Visitor instead of in the ConcreteElements.

At the same time, there is no clutter in the ConcreteElement classes with many methods on each of them. The ConcreteVisitors take care of the methods and integrate them in the same class following a trasversal line.

Disadvantages

There are many changes introduced in the Visitor hierarchy when an Element is added: a new visit() method must be implemented by all Visitors. Moreover, the Api is not complete when you read the code of a ConcreteElement class, since a Visitor can do anything. This can be an hindrance when trying to understanding what code can do.

Examples

The code sample regards a data structure that manages GET or POST input data, being it single or multiple values (like the ones coming from a <select multiple>). A pair of Visitors define operations on these data structures.

<?php

/**
* An Element class.
* Defining this as an interface or abstract class is equivalent.
*/
abstract class InputValue
{
private $_value;

public function __construct($value)
{
$this->set($value);
}

public function set($value)
{
$this->_value = $value;
}

public function get()
{
return $this->_value;
}

public abstract function acceptVisitor(Visitor $visitor);
}

/**
* A ConcreteElement. Accepts a Visitor and forwards to it on its specialized method.
*/
class SingleInputValue extends InputValue
{
public function acceptVisitor(Visitor $visitor)
{
$visitor->visitSingleInputValue($this);
}
}

/**
* Another ConcreteElement.
*/
class MultipleInputValue extends InputValue
{
public function acceptVisitor(Visitor $visitor)
{
$visitor->visitMultipleInputValue($this);
}
}

/**
* The Visitor participant. Again, interface or abstract classes are equivalent.
*/
interface Visitor
{
/**
* Since in PHP there is no simple mechanism for method overloading,
* the visit() methods must not only have different parameters
* but different names too.
*/
public function visitSingleInputValue(SingleInputValue $inputValue);
public function visitMultipleInputValue(MultipleInputValue $inputValue);
}

/**
* A ConcreteVisitor.
* Filters all the values provided casting them to integers.
*/
class IntFilter implements Visitor
{
public function visitSingleInputValue(SingleInputValue $inputValue)
{
$inputValue->set((int) $inputValue->get());
}

public function visitMultipleInputValue(MultipleInputValue $inputValue)
{
$newValues = array();
foreach ($inputValue->get() as $index => $value) {
$newValues[$index] = (int) $value;
}
$inputValue->set($newValues);
}
}

/**
* Another ConcreteVisitor.
* Sorts multiple values.
*/
class AscendingSort implements Visitor
{
/**
* Do nothing. This part is equivalent to a Null Object,
* which leverages polymorphism to achieve more concise code.
*/
public function visitSingleInputValue(SingleInputValue $inputValue)
{
}

public function visitMultipleInputValue(MultipleInputValue $inputValue)
{
$values = $inputValue->get();
asort($values);
$inputValue->set($values);
}
}

// client code
$userId = new SingleInputValue("42");
$categories = new MultipleInputValue(array('hated' => 16, 'ordinary' => 23, 'preferred' => 15));
$userId->acceptVisitor(new IntFilter);
var_dump($userId->get());
$categories->acceptVisitor(new AscendingSort);
var_dump($categories->get());

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}