Monday, January 18, 2010

Practical Php Patterns: Bridge

This post is part of the Practical Php Pattern series.

Today we will explore the Bridge pattern, whose intent is separating an abstraction from its implementation to improve decoupling and allowing them both to change independently.
Commonly an abstraction is thought as an interface implemented by several concrete classes; but there are other means to obtain the same result.
The Bridge pattern is one of these ways - it is similar to the Adapter pattern, but its underlying motivation is different: an Adapter makes two unrelated, existing classes work together, when the two participants were not thought to be aware of each other during design, or they must not be coupled as part of different modules. A Bridge is a structure that separates concerns and which is chosen at the design level, before the creation of the participants classes.
The Abstraction that a Bridge shows to the Client is a class that composes an Implementor interface. Two trees may develop: the abstraction hierarchy and the implementation one. Without this differentiation, the two hierarchies would be merged and the result would be a growing number of classes needed to cover the combinations of RefinedAbstractions and ConcreteImplementors.
This pattern is a powerful example of favoring composition over inheritance, one of the motifs of the GoF design patterns book. The link between abstraction and implementation consists in an has-a relationship whose target is set at runtime, and that can be extended without the end user of the Abstraction noticing.

Participants:
  • Abstraction: the ultimate interface that the Client sees.
  • Implementor: the segregated interface created for providers of an actual implementation.
  • RefinedAbstraction: extension of the Abstraction functionalities.
  • ConcreteImplementor: realization of Implementor.
The code sample shows a Bridge implementation for a Website data structure used to store a search engine's data.
<?php
/**
 * SECTION 1: Implementor hierarchy.
 * Implementor: a small bean that provides basic methods for accessing
 * data on a single Website.
 */
interface WebsiteImplementor
{
    /**
     * @param string $address
     * @return boolean
     */
    public function containsLinkTo($address);

    /**
     * @return string
     */
    public function getAddress();
}

/**
 * ConcreteImplementor.
 * Another implementation could be CrawlerWebsiteImplementor which scans pages
 * on a as-needed basis to find links.
 */
class IndexedWebsiteImplementor implements WebsiteImplementor
{
    private $_address;
    private $_links;

    public function __construct($address, array $linked)
    {
        $this->_address = $address;
        $this->_links = $linked;
    }

    public function getAddress()
    {
        return $this->_address;
    }

    public function containsLinkTo($address)
    {
        return in_array($address, $this->_links);
    }
}

/**
 * SECTION 2: Abstraction hierarchy.
 * Abstraction.
 * Additional operation can be concrete methods on an abstract class,
 * but a Bridge frees the Implementors from extending a single base class.
 * For example, different Abstractions can use the same Implementor
 * objects and classes if necessary, while if Implementor extends something
 * it remains tied to the base class.
 */
class Website
{
    private $_imp;

    public function __construct(WebsiteImplementor $imp)
    {
        $this->_imp = $imp;
    }

    public function getAddress()
    {
        return $this->_imp->getAddress();
    }

    public function containsLinkTo($anotherWebsite)
    {
        if ($anotherWebsite instanceof Website) {
            $anotherWebsite = $anotherWebsite->getAddress();
        }
        return $this->_imp->containsLinkTo($anotherWebsite);
    }

    public function containsLinks(array $addresses)
    {
        $result = true;
        foreach ($addresses as $a) {
            if (!$this->containsLinkTo($a)) {
                $result = false;
            }
        }
        return $result;
    }
}

/**
 * RefinedAbstraction for presentation purposes.
 * It would not be possible to create a PresentationWebsite which presents
 * the same interface to the Client *for every type of WebsiteImplementor*
 * if we were using an abstract class (that ConcreteImplementors extend)
 * for the Abstraction operations.
 * In that case, we would have been forced to create PresentationIndexedWebsite,
 * PresentationCrawlerWebsite, ...
 */
class PresentationWebsite extends Website
{
    public function getAddress()
    {
        $address = 'http://' . parent::getAddress();
        return "<a href=\"$address\">$address</a>";
    }
}

$links = array('www.google.com', 'www.twitter.com', 'www.youtube.com', 'giorgiosironi.blogspot.com');
$websiteImp = new IndexedWebsiteImplementor('www.blogger.com', $links);
$website = new Website($websiteImp);
var_dump($website->containsLinks(array('www.google.com', 'www.youtube.com')));
$presentationWebsite = new PresentationWebsite($websiteImp);
echo $presentationWebsite->getAddress(), "\n";
The Bridge pattern is typically used for:
  • avoiding an hardcoded relationship between an abstraction and its implementations. An abstract class which is extended by implementors becomes a class which composes an implementation of the missing functionalities.
  • Keeping the possibility of adding methods to an abstraction; adding methods to an interface breaks the implementations, while adding them to the Abstraction does not cause errors as long as they contain code. This possibility is widely used in libraries which have to deal with abstractions evolution and backward compatibility: some of them provide distinct Application Programming Interface for Clients and Service Programming Interface for ConcreteImplementors (more details).
  • Extending this choice further, the Implementor may really define only basic level operations, while the Abstraction centralizes higher-level methods that build on the functionalities of the Implementor. For instance if the Implementor defines the operation hasMethod(), the Abstraction can present also an hasMethods() one, preventing the code of the latter from duplication in all Implementors or Clients.
  • Segregating an interface when it grows to comprehend very similar methods, and its implementations duplicate code to satisfy the requirements accordingly. It might be the case to provide the higher-level operations on an Abstraction class which composes the Implementor interface (as presented in the code sample).

1 comment:

  1. that's the most non practical hard to understand example i've seen.

    ReplyDelete