Practical PHP Patterns: Mediator
The pattern of the day is the Mediator one. The intent of this pattern is encapsulating the interactions of a set of objects, preventing aggressive coupling from each of them towards the other ones. A Mediator acts as a central point of convergence between the Colleague objects.In any domain there are many operations that do not fit on existent, modelled-from-reality objects, and if forced as methods of an already existent class would add a dependency towards a possibly unrelated collaborator. This approach would result in a web of highly interrelated Colleague objects and not in the Ravioli code we want to work with. The Colleague objects should be kept loosely coupled to avoid having to reference them as a whole any time one of them is needed from a Client.
The solution to this common problem is implementing the Mediator pattern. When an object's relationships and dependencies start to conflict with its business responsibility (the reason it was created in the first place), we should introduce a Mediator that coordinates the workflow between the coupled objects, freeing them from this form of coupling; dependencies can be established from the Colleagues towards the Mediator and/or from the Mediator towards the Colleagues. Both directions of those dependencies can be broken with an interface AbstractColleague or AbstractMediator, if necessary.
No object is an island, and each object in an application must cooperate with other parts of the graph to get its job done and addressing one concern. Since the interactions are a source of coupling, a Mediator is one of the most effective patterns in limiting it, although, if abused, it may render more difficult to write cohesive classes.
As a practical example, Services in Domain-Driven Design are Mediators between Entities. For a php-related example, Zend_Form decorating and filtering capabilities are actually the implementation of a simple Mediator between Zend_Form_Decorator and Zend_Filter instances. The same goes for validation using Zend_Validate objects. Making every filter referencing the next one would build a Chain of Responsibility which potential would be unused.
When a Mediator must listen to Colleagues events, it is often implemented as an Observer resulting in a blackboard object where some Colleagues write and other ones read. Events are pushed to the Mediator from a Colleague, before it delivers them to the others subscribed Colleagues. There is no knowledge of others Colleagues in anyone of them: this architecture is successfully used in the Dojo javascript library shipped with Zend Framework.
Another advantage of this pattern is the variation of the objects involved in the computation: this goal can be achived by configuring the Mediator differently, whereas instancing interrelated objects would be an noncohesive operation and the collaboration relationships would be scattered between different containers or factories.
Participants
- Colleague: focuses on its responsibility, communicating only with a Mediator or AbstractMediator.
- Mediator: coordinates the work of a set composed by several Colleagues (AbstractColleagues).
- AbstractMediator, AbstractColleague: optional interfaces that decouple from the actual implementation of these roles. There may be more than one AbstractColleague role.
<?php
/**
* AbstractColleague.
*/
interface Filter
{
public function filter($value);
}
/**
* Colleague. We decide in the implementation phase
* that Colleagues should not know the next Colleague
* in the chain, resorting to a Mediator to link them together.
* This choice succesfully avoids a base abstract class
* for Filters.
* Remember that this is an example: it is not only
* Chain of Responsibility that can be alternatively implemented
* as a Mediator.
*/
class TrimFilter implements Filter
{
public function filter($value)
{
return trim($value);
}
}
/**
* Colleague.
*/
class NullFilter implements Filter
{
public function filter($value)
{
return $value ? $value : '';
}
}
/**
* Colleague.
*/
class HtmlEntitiesFilter implements Filter
{
public function filter($value)
{
return htmlentities($value);
}
}
/**
* The Mediator. We avoid referencing it from ConcreteColleagues
* and so the need for an interface. We leave the implementation
* of a bidirectional channel for the Observer pattern's example.
* This class responsibility is to store the value and coordinate
* filters computation when they have to be applied to the value.
* Filtering responsibilities are obviously a concern of
* the Colleagues, which are Filter implementations.
*/
class InputElement
{
protected $_filters;
protected $_value;
public function addFilter(Filter $filter)
{
$this->_filters[] = $filter;
return $this;
}
public function setValue($value)
{
$this->_value = $this->_filter($value);
}
protected function _filter($value)
{
foreach ($this->_filters as $filter) {
$value = $filter->filter($value);
}
return $value;
}
public function getValue()
{
return $this->_value;
}
}
$input = new InputElement();
$input->addFilter(new NullFilter())
->addFilter(new TrimFilter())
->addFilter(new HtmlEntitiesFilter());
$input->setValue(' You should use the <h1>-<h6> tags for your headings.');
echo $input->getValue(), "\n";