Table of Contents

Having seen the Strategy pattern, it’s time to look at two related patterns, which use the same notion of a unified API to control a collection of (possibly) unrelated objects.

Both of these patterns give us the opportunity to turn our applications into something much “bigger” than we origionally planned, with minimal effort required to re-write code.

1)

The Adapter Pattern

The adapter pattern is conceptually a bit like an international power adapter that you take on holiday, so you can plug in your electric shaver or hairdryer to the local power supply.

You use an adapter pattern when you’ve already built a class to perform some task but now need to an alternative class (with perhaps a very different API) to perform the same function.

It’s similar to the Strategy pattern but where the Strategy pattern deals with hiding a set of algorithms behind a common interface, the Adapter pattern deals with hiding a collection of objects behind an interface.

Note: there are two variants of the Adapter pattern: the Object Adapter (which is what we’re talking about here) and the Class Adapter (which requires multiple inheritance, not supported by PHP).

It’s best demonstrated with an example. We’ve looked at the MVC Pattern.

Working, in general, from that example, which used MySQL as the target database, imagine now that we have customers for our “Products” application. Customer A says “I love it but I use Oracle”. Customer B says “I want to deploy you application in my N-Tier environment using SOAP to provide your application with data”.

So here we have two problems. For Oracle, simple database abstraction isn’t enough, because MySQL uses an SQL syntax which often varies significantly from Oracle. For example, imagine we want limit the number of rows we return from a SELECT statement.

In MySQL we can use;

SELECT * FROM products
ORDER BY productname LIMIT 50, 20

to select rows 50 - 70.

In Oracle, we need the following to achieve the same result set;

SELECT
 *
FROM
    (SELECT
      ROW_NUMBER() OVER (ORDER BY productname) RN, *
     FROM
      table
     ORDER BY
      productname)
WHERE
 RN
BETWEEN
 50 AND 70

[Nice huh?]

Then we have the problem of the other customer who’s providing use the data via SOAP (we’ll assume they’ve provided methods which correspond to our database calls). For SOAP we’re not even talking about SQL statements. What now?

Simply put, to avoid writing a new version of our application for each customer we need an adapter pattern. We’ll take our origional ProductModel class, which was designed for MySQL and “adapt” it so that we can use other objects via the same API (and so prevent the need to re-write the rest of our code).

For this example, I’m not going to set up an example that actually works (i.e. I’m not writing the Oracle or SOAP classes) but will just demonstrate the adapter an you’ll have to imagine the underlying implementations.

So here’s how things might look applying the adapter pattern to our earlier MVC example, in terms of UML;

[ This time I used Dia to draw the diagram ]

Let’s have a look now at the ProductModelAdapter class;

<?php
class ProductModelAdapter extends ProductModel {
    /**
    * Private
    * $adaptee an instance of the DataAccess class
    */
    var $adaptee;
 
    /**
    * Private
    * $adapteeType to identify which adaptee we're using
    */
    var $adapteeType;
 
    /**
    * Private
    * $dao an instance of the DataAccess class
    */
    var $dao;
 
    //! A constructor.
    /**
    * Constucts a new ProductModel object
    * @param $dao an instance an Oracle or SOAP access object
    * @param $adapteeType - an adaptee name for switch statements
    */
    function ProductModelAdapter (& $dao, $adapteeType) {
        switch ( $adaptee ) {
            case 'SOAP':
                $this->adaptee=& new SOAPModel($dao);
                break;
            case 'Oracle':
                $this->adaptee=& new OracleModel($dao);
                break;
        }
        $this->adapteeType=$adapteeType;
        $this->dao=& $dao;
    }
 
   //! A manipulator
    /**
    * Tells the $dboject to store this query as a resource
    * @param $start the row to start from
    * @param $rows the number of rows to fetch
    * @return void
    */
    function listProducts ($start=1,$rows=50) {
        switch ( $this->adaptee ) {
            case 'SOAP':
                $this->adaptee->callProducts($start,$rows);
                break;
            case 'Oracle':
                $this->adaptee->listProducts($start,$rows);
                break;
        }
    }
 
    //! A manipulator
    /**
    * Tells the $dboject to store this query as a resource
    * @param $id a primary key for a row
    * @return void
    */
    function listProduct ($id) {
        switch ( $this->adaptee ) {
            case 'SOAP':
                $this->adaptee->callProduct($id);
                break;
            case 'Oracle':
                $this->adaptee->listProduct($id);
                break;
        }
    }
 
    //! A manipulator
    /**
    * Fetches a product as an associative array from the $dbobject
    * @return mixed
    */
    function getProduct () {
        switch ( $this->adaptee ) {
            case 'SOAP':
                return $this->adaptee->getCacheRow();
                break;
            case 'Oracle':
                return $this->adaptee->getProduct();
                break;
        }
    }
}
?>

[Note I’m not saying this is the most “cunning” way to implement this class but it demonstrates the point]

The important thing to notice is the API for this class is exactly the same as the ProductModel class we built for the MVC pattern. But using the adapter class, we can call “adaptee” methods which even have different names.

Now to use the adapter, rather than re-writing a whole load of code to use the specific methods of the “adaptee” classes, we simply need to update the constructor of ProductController class;

<?php
/**
 *  Controls the application
 */
class ProductController {
    var $model;
    var $view;
 
    //! A constructor.
    /**
    * Constucts a new ProductController object
    * @param $model an instance of the ProductModel class
    * @param $getvars the incoming HTTP GET method variables
    */
    function ProductController (& $dao,$adapter=false) {
        if ($adapter)
            $this->model=& new ProductModelAdapter($dao);
        else
            $this->model=& new ProductModel($dao);
    }
}
 
class ProductItemController extends ProductController {
   //! A constructor.
    /**
    * Constucts a new ProductItemController object
    * @param $model an instance of the ProductModel class
    * @param $getvars the incoming HTTP GET method variables
    */
    function ProductItemController (& $dao,$getvars=null,$adapter=false) {
        ProductController::ProductController($dao,$adapter);
        $this->view=& new ProductItemView($this->model,$getvars['id']);
    }
 
    function & getView () {
        return $this->view;
    }
}
 
class ProductTableController extends ProductController {
   //! A constructor.
    /**
    * Constucts a new ProductTableController object
    * @param $model an instance of the ProductModel class
    * @param $getvars the incoming HTTP GET method variables
    */
    function ProductTableController (& $dao,$getvars=null,$adapter=false) {
        ProductController::ProductController($dao,$adapter);
        if ( !isset ($getvars['rowsperpage']) )
            $rowsperpage=20;
        if ( !isset ($getvars['rownum']) )
            $getvars['rownum']=1;
        $this->view=& new ProductTableView($this->model,
                                    $rowsperpage,
                                    $getvars['rownum']);
    }
 
    function & getView () {
        return $this->view;
    }
}
?>

Notice that in the construtor of ProductController we set it up to use the adapter if we need it.

And finish off by updating index.php to fire up the right $dao and include the required libraries;

<?php
require_once('lib/DataAccess.php');
require_once('lib/WSDLClient.php');
require_once('lib/OracleDataAccess.php');
require_once('lib/ProductModel.php');
require_once('lib/ProductModelAdapter.php');
require_once('lib/SOAPModel.php');
require_once('lib/OracleModel.php');
require_once('lib/ProductView.php');
require_once('lib/ProductController.php');

$adaptee='SOAP'; // set this to the required adaptee
$adapter=true; // Switch on or off to use the adapter

switch ( $adaptee ) {
    case 'SOAP':
        $dao=& new WSDLClient ('http://www.domain.com/products.wsdl',
                               'user',
                               'pass');
        break;
    case 'Oracle':
        $dao=& new OracleDataAccess ('localhost','user','pass','dbname');
        break;
    default:
        $dao=& new DataAccess ('localhost','user','pass','dbname');
        break;
}

switch ( $_GET['view'] ) {
    case "product":
        $controller=& new ProductItemController($dao,$_GET,$adapter);
        break;
    default:
        $controller=& new ProductTableController($dao,$_GET,$adapter);
        break;
}
$view=$controller->getView();
echo ($view->display());
?>

What happens now is by default our application will use the origional MySQL data source but if need to, we can set up the environment to use the adapter itself, which will use Oracle or SOAP (or anything else we care to add) to fetch the data for our application.

If we hadn’t used the Adapter pattern, we would have ended up having to re-write our ProductView class, at least in the case of the SOAP data source and had the OracleModel class has a different API to ProductModel, we would have needed a new version of ProductView for that as well.

The Proxy Pattern

The proxy pattern is very similar in concept to the adapter pattern - it provides a common API for multiple objects which could be varying in nature.

In general, the difference between the proxy and adapter pattern is you design your proxy first, the intention from the start being all client objects will use only the proxy API.

Perhaps the best example of a proxy pattern (that I’ve come across) in PHP is XML-RPC Class Server, which allows you to turn a collection of PHP classes into XML-RPC methods for remote invokation over a network, using the URL as the means to send a method name and associated parameters. You can even proxy of classes on remote servers! What’s most impressive is how little code is used in the class server to achieve this functionality.

Proxy patterns are also being used by the PEAR::SOAP and NuSOAP PHP SOAP libraries, to read WSDL documents and automatically generate SOAP clients. This is put to best effect with ActiveStates Simple Web Services API where building a SOAP client get’s this simple (with a little help from PEAR::SOAP);

<?php
$AirportWeather = WebService::ServiceProxy(
    "http://www.capescience.com/webservices/airportweather/AirportWeather.wsdl");
$result = $AirportWeather->getTemperature("KLAX"); # Los Angeles
?>

The proxy API is returning an object which we can they use to class SOAP methods by name (whether this strictly adheres to the definition of a proxy patterns I can’t say - but I like it!).

Resources


design/adapters_and_proxy_patterns.txt · Last modified: 2005/10/15 21:47