Implementing the Delegation Pattern Using Reflection


Times arise where a class (One) is supposed to do everything another class (Two) does and more. The preliminary temptation would be for class One to extend class Two , and thereby inheriting all of its functionality. However, there are times when this is the wrong thing to do, either because there isn’t a clear semantic is-a relationship between classes One and Two , or class One is already extending another class, and inheritance cannot be used. Under such circumstances, it is useful to use a delegation model (via the delegation design pattern), where method calls that class One can’t handle are redirected to class Two . In some cases, you may even want to chain a larger number of objects where the first one in the list has highest priority. The following example creates such a delegator called ClassOneDelegator that first checks if the method exists and is accessible in ClassOne ; if not, it tries all other objects that are registered with it. The application can register additional objects that should be delegated to by using the addObject($obj) method. The order of adding the objects is the order of precedence when Class OneDelegator searches for an object that can satisfy the request:

class ClassOne {
	function callClassOne() {
		print "In Class One\n";
	}
}

class ClassTwo {
	function callClassTwo() {
		print "In Class Two\n";
	}
}

class ClassOneDelegator {
	private $target;

	function __construct() {
		$this->target[] = new ClassOne();
	}

	function addObject($obj) {
		$this->target[] = $obj;
	}

	function __call($name, $args) {
		foreach ($this->target as $obj) {
			$r = new ReflectionClass($obj);
			if (!$r->hasMethod($name)) {
				continue;
			}

			$method = $r->getMethod($name);
			if ($method->isPublic() && !$method->isAbstract()) {
				return $method->invoke($obj, $args);
			}
		}
	}
}

$obj = new ClassOneDelegator();
$obj->addObject(new ClassTwo());
$obj->callClassOne();
$obj->callClassTwo();

Running this code results in the following output:

In Class One
In Class Two

You can see that this example uses the previously described feature of overloading method calls using the special __call() method. After the call is intercepted, __call() uses the reflection API to search for an object that can satisfy the request. Such an object is defined as an object that has a method with the same name, which is publicly accessible and is not an abstract method.

Currently, the code does nothing if no satisfying function is found. You may want to call ClassOne by default, so that you make PHP error out with a nice error message, and in case ClassOne has itself defined a __call() method, it would be called. It is up to you to implement the default case in a way that suits your needs.

PHP 5 Power Programming. Sample Chapter is provided courtesy of Prentice Hall PTR.

Buy this book.


3 responses to “Implementing the Delegation Pattern Using Reflection”

  1. This is a good idea! but I would like to clarify something. This is not true delegation, this is forwarding.
    The difference between delegation and forwarding is that when you forward the “method call” (I prefer to say “message” because is more object oriented ;D) that is received by an object “a” of class “A” to another object “b” of class “B”, “b” receives the call as an instance of B, so if inside the method there is something like $this->someMethod(), someMethod will be called in class B.
    In true delegation that won’t happen, but I don’t think that it could be implemented in PHP. The only language that I could implement it was in Smalltalk and it was a headache.
    Anyway I like your post!
    Gaboto

  2. I got this result in my pc when running:
    X-Powered-By: PHP/5.2.5
    Content-type: text/html

    In Class One

    Fatal error: Uncaught exception ‘ReflectionException’ with message ‘Method callClassTwo does not exist’

Leave a comment