Proxies, Delegates en Decorators met PHP5
Sinds PHP5 is er de mogelijkheid om aanroepen van methodes en aanroepen naar members af te vangen met eigen code. Deze methodes (__get, __set en __call) bieden je de mogelijkheid om zeer generieke proxies, decorators en delegators te implementeren.
Proxies, decorators en delegators hebben 1 ding in gemeen: ze wijzigen alle drie het gedrag van het onderliggende object door een van de methoden te overriden terwijl de rest van de methoden in principe hetzelfde blijft.
Het is niet de bedoeling dat deze post alleen maar theoretisch is, dus laten we een simpel voorbeeld gebruiken om het concept te demonstreren.
Stel dat we de volgende klasse hebben:
-
class HelloWorld
-
{
-
function sayHello()
-
{
-
return "Hello World";
-
}
-
-
function doSomethingElse()
-
{
-
}
-
}
-
-
$obj = new HelloWorld();
Vervolgens willen we ook enkele klassen maken welke het hallo-gezeg decoreren: een decorator om de tekst bold te maken en nog een andere om de tekst italic te maken. Het is natuurlijk mogelijk om een klasse te maken welke overerft van HelloWorld maar daar heb ik het later nog even over. Neem voor nu even aan dat overerving niet mogelijk zou zijn.
Normaal gesproken als ik de sayHello() methode zou willen uitbreiden via een Decorator patroon zou ik het volgende object maken:
-
class BoldHelloWorld
-
{
-
var $m_object = NULL;
-
-
function BoldHelloWorld($object)
-
{
-
$this->m_object = $object;
-
}
-
-
function sayHello()
-
{
-
return "<b>".$this->m_object->sayHello()."</b>";
-
}
-
-
function doSomethingElse()
-
{
-
return $this->m_object->doSomethingElse();
-
}
-
}
-
-
$obj = new BoldHelloWorld(new HelloWorld());
Dit zou keurig werken maar er zit een probleempje in het gebruik van de doSomethingElse() methode. Nouja, niet echt een probleem, het is alleen zo dat om de interface van het te decoreren object niet te veranderen, ik deze methode moet definieren in de nieuwe klasse zodat de aanroep wordt geforward naar het originele object. Naarmate je meer methodes hebt zul je meer code moeten toevoegen om al die methodes te forwarden. Hetzelfde probleem zou je hebben met eventuele member variabelen. Die zouden ook op de zelfde manier geforward moeten worden (ik weet het, public member variabelen zijn Evil, maar soms is de klasse die je gebruikt niet door jou ontwikkeld en zul je er gewoon omheen moeten werken).
PHP5 biedt dus de nodige features om dit probleem op te lossen, en het is nog makkelijk ook. De magic methods, __call, __get en __set stellen ons in staat om een generieke basis klasse die dit forwarding probleem op lost te bouwen (de credits voor deze klasse zijn voor mijn collega's Martin en Peter).
-
/**
-
AutoForward baseclass for automatic forwarding of
-
method calls and member variables.
-
-
@author Peter C. Verhage
-
@author Martin Roest
-
*/
-
class AutoForward
-
{
-
var $m_object;
-
-
/**
-
Constructor.
-
-
@param Object $object
-
*/
-
function __construct(&$object)
-
{
-
$this->m_object = $object;
-
}
-
-
/**
-
Returns the forwarded object.
-
*/
-
function &__getObject()
-
{
-
return $this->m_object;
-
}
-
-
/**
-
Forward method calls.
-
-
@param String $method method name
-
@param Array $args method arguments
-
@return Unknown method return value
-
*/
-
function __call($method, $args)
-
{
-
}
-
-
/**
-
Forward property set.
-
-
@param String $name property name
-
@param Unknown $value property value
-
*/
-
function __set($name, $value)
-
{
-
$this->m_object->$name = $value;
-
}
-
-
/**
-
Forward property get.
-
-
@param String $name, property name
-
@return Unknown
-
*/
-
function __get($name)
-
{
-
return $this->m_object->$name;
-
}
-
}
Door de zogenaamde magic methodes __set, __get en _call te overriden worden alle methoden van de AutoForward klasse direct geforward naar het interne object in m_object.
Laten we deze klasse gebruiken om onze Bold en Italic decorators te ontwikkelen:
-
class BoldHelloWorld extends AutoForward
-
{
-
function sayHello()
-
{
-
return "<b>".$this->m_object->sayHello()."</b>";
-
}
-
}
-
-
class ItalicHelloWorld extends AutoForward
-
{
-
function sayHello()
-
{
-
return "<i>".$this->m_object->sayHello()."</i>";
-
}
-
}
-
-
$obj = new ItalicHelloWorld(new HelloWorld());
Nu hoeven we niet langer ook de doSomethingElse methode definieren! De aanroep wordt door de AutoForward klasse automatisch geforward naar het originele object. Een object van het type ItalicHelloWorld zal identiek reageren als een HelloWorld object. Een aanroeper zou geen verschil merken, buiten dan misschien het andere resultaat.
Ik heb beloofd uit te leggen waarom overerving geen oplossing zou zijn. Het zit hem in de ItalicHelloWorld en BoldHelloWorld klassen. Wat nu als je een resultaat wilde hebben dat zowel Bold als Italic was? Welke van de twee zou je gebruiken? PHP ondersteunt geen Multiple Inheritance dus je zou aardig vast lopen. Maar niet met de AutoForward klasse! Met ons systeem zou het simpelweg een kwestie zijn van:
-
$obj = new BoldHelloWorld(
-
new ItalicHelloWorld (
-
new HelloWorld()
-
)
-
);
-
Op deze manier hebben we helemaal geen Multiple Inheritance nodig!
Magic methods en Aspect Oriented Programming
Deze techniek is ook erg handig als je een fan bent van het zogenaamde Aspect Oriented Programming. Je zou een object kunnen hebben dat een bepaald aspect implementeert dat van toepassing is op het onderliggende object.
In plaats van een enkele methode te overriden kun je __call overriden en een daar een aspect implementeren. Laten we security als voorbeeld nemen. Stel we hebben een object maar we willen de toegang tot de members beveiligen. We zouden daarvoor de volgende klasse kunnen gebruiken:
-
class SecurityAspect extends AutoForward
-
{
-
function __call($method, $args)
-
{
-
if (isAllowed($method))
-
{
-
}
-
throw new Exception("Caller not allowed to execute $method on this object.");
-
}
-
}
-
-
$obj = new SecurityAspect(new HelloWorld());
-
$obj->sayHello(); // throws error if sayHello not allowed
Een ander makkelijk te implementeren aspect is caching van resource intensieve methodes. Definieer een lijst van methodes om te cachen en een member variabele om de informatie in te cachen et voila!
Conclusie
Met deze post heb ik geprobeerd om de kracht van PHP5's magic methodes te laten zien. Java heeft gelijkwaardige oplossingen door het gebruik van Reflection maar met PHP5 is het veel eenvoudiger om dergelijke constructies samen te stellen. Dit stelt je in staat om kleine patronen met weinig code te definieren. Voel je vrij om de code uit dit artikel te gebruiken!
Over de auteur
Dit artikel is een met toestemming vertaalde versie van een blogpost van Ivo Jansch. Ivo is technical director bij ibuildings.nl en werkt o.a. aan het open source Achievo ATK framework.
Vertaald door Mathieu Kooiman.
Volg Scriptorama via RSS!
Reageer ook!
Kleine toevoeging: met de decorator pattern voorkom je dus classes als BoldAndItalicsHelloWorld. Dit concept kun je makkelijk vertalen naar het klassieke voorbeeld van "kosten berekenen" :)
Door Tri Pham
op 07.06.06 @ 2:02 pm | Permalink
Zie ook Harry Fuecks' prima bronnen:
http://www.sitepoint.com/article/coming-soon-webserver-near/8
http://www.phppatterns.com/docs/design/adapters_and_proxy_patterns
http://www.phppatterns.com/docs/design/decorator_pattern
Door Michel
op 07.06.06 @ 7:19 pm | Permalink
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>