2009-06-04 12 views
28

Supponiamo che esista una classe denominata "Class_A", che abbia una funzione membro denominata "func".Come implementare un decoratore in PHP?

Voglio il "func" per fare qualche lavoro extra avvolgendo Class_A in una classe decoratore.

$worker = new Decorator(new Original()); 

Qualcuno può dare un esempio? Non ho mai usato OO con PHP.

La seguente versione è corretta?

class Decorator 
{ 
    protected $jobs2do; 

    public function __construct($string) { 
     $this->jobs2do[] = $this->do; 
    } 

    public function do() { 
     // ... 
    } 
} 

Il codice precedente intende apportare un lavoro aggiuntivo a un array.

+1

Si dovrebbe inizializzare la variabile: protected $ jobs2do = array(); – soulmerge

+0

Ma questa non è ancora la versione standard, forse dovrebbe usare qualcosa come "call_user_func" – omg

+0

Oh, non l'ho visto! Sì, certo, non ci sono chiusure prima di PHP 5.3. Dovresti assolutamente dare un'occhiata ai callback in PHP: http://us.php.net/call-user-func – soulmerge

risposta

34

Questo è abbastanza facile, soprattutto in un linguaggio tipizzato in modo dinamico come PHP:

class Text { 

    protected $string; 

    /** 
    * @param string $string 
    */ 
    public function __construct($string) { 
     $this->string = $string; 
    } 

    public function __toString() { 
     return $this->string; 
    } 
} 

class LeetText { 

    protected $text; 

    /** 
    * @param Text $text A Text object. 
    */ 
    public function __construct($text) { 
     $this->text = $text; 
    } 

    public function __toString() { 
     return strtr($this->text->__toString(), 'eilto', '31170'); 
    } 
} 

$text = new LeetText(new Text('Hello world')); 
echo $text; // H3110 w0r1d 

Si consiglia di avere uno sguardo alla wikipedia article, anche.

+0

Grazie, ma come impilare una nuova funzione membro in un array? Sembra che la mia versione attuale causerà un avvertimento. – omg

+0

Perché $ stringa e $ testo non sono privati? –

+0

Puoi renderlo privato ovviamente :) – omg

38

Suggerirei anche di creare un'interfaccia unificata (o anche una classe base astratta) per i decoratori e gli oggetti che si desiderano decorare.

Per continuare l'esempio di cui sopra a condizione che si potrebbe avere qualcosa di simile:

interface IDecoratedText 
{ 
    public function __toString(); 
} 

Poi, naturalmente, modificare siaText e LeetText per implementare l'interfaccia.

class Text implements IDecoratedText 
{ 
...//same implementation as above 
} 

class LeetText implements IDecoratedText 
{  
    protected $text; 

    public function __construct(IDecoratedText $text) { 
     $this->text = $text; 
    } 

    public function __toString() { 
     return str_replace(array('e', 'i', 'l', 't', 'o'), array(3, 1, 1, 7, 0), $this->text->toString()); 
    } 

} 

Perché utilizzare un'interfaccia?

Perché è possibile aggiungere tutti i decoratori che si desiderano e assicurarsi che ogni decoratore (o oggetto da decorare) disponga di tutte le funzionalità richieste.

+1

Ecco perché ho detto che è particolarmente facile in PHP: non ci sono bisogno di cose del genere :) Se vuoi davvero la sicurezza del tipo, dovresti usare un'altra lingua. – soulmerge

+0

Non vedo i vantaggi di utilizzare un'interfaccia qui –

+0

Grazie, ma come impilare una nuova funzione membro in un array? Sembra che la mia versione attuale causerà un avviso. – omg

5

Volevo usare la decorazione per incoraggiare i colleghi ad usare la cache di più, e ispirato dalla bella sintassi Python sperimentata con il reflection di PHP per simulare questa caratteristica del linguaggio (e devo sottolineare 'falso'). Era un approccio utile. Ecco un esempio:

class MrClass { 
    /** 
    * decoratorname-paramname: 50 
    * decoratorname-paramname2: 30 
    */ 
    public function a_method($args) { 
    // do some stuff 
    } 
} 


class DecoratorClass { 
    public function __construct($obj) { 
    $this->obj = $obj; 
    $this->refl = new ReflectionClass($obj); 
    } 
    public function __call($name, $args) { 
    $method = $this->refl->getMethod($name); 
    // get method's doccomment 
    $com = trim($method->getDocComment()); 
    // extract decorator params from $com 
    $ret = call_user_func_array(array($this->obj, $name), $args); 
    // perhaps modify $ret based on things found in $com 
    return $ret; 
} 

Meglio esempi con esempi di caching qui: https://github.com/mrmonkington/EggCup/

+0

Questo è più tipo di Proxy Pattern !! –

20

Nessuna di queste risposte realizza Decorator modo corretto e con eleganza. La risposta di mrmonkington si avvicina, ma non è necessario utilizzare la riflessione per applicare lo schema Decorator in PHP. In un'altra discussione, @Gordon shows how to use a decorator for logging SOAP activity. Ecco come lo fa:

class SoapClientLogger 
{ 
    protected $soapClient; 

    // this is standard. Use your constuctor to set up a reference to the decorated object. 
    public function __construct(SoapClient $client) 
    { 
     $this->soapClient = $client; 
    } 

    ... overridden and/or new methods here ... 

    // route all other method calls directly to soapClient 
    public function __call($method, $args) 
    { 
     // you could also add method_exists check here 
     return call_user_func_array(array($this->soapClient, $method), $args); 
    } 
} 

E lui è una leggera modifica in cui è possibile passare la funzionalità desiderata al costruttore:

class Decorator { 

    private $o; 

    public function __construct($object, $function_name, $function) { 
     $this->o = $object; 
     $this->$function_name = $function; 
    } 
    public function __call($method, $args) 
    { 
     if (!method_exists($this->o, $method)) { 
      throw new Exception("Undefined method $method attempt in the Url class here."); 
     } 
     return call_user_func_array(array($this->o, $method), $args); 
    } 
} 
+3

Dovresti migliorare il metodo __call() per supportare gli oggetti che implementano il pattern fluente-api. qualcosa come '$ result = call_user_func_array (array ($ this-> o, $ method), $ args); return $ result === $ this-> o? $ this: $ result; ' – Nat

+0

spiegare l'utilizzo e il flusso di lavoro di $ nome_funzione – Massimo

-2

Una caratteristica fondamentale e unica del Decorator è che una classe astratta estende un altro. Il partecipante di Decorator è sia un wrapper che estende il partecipante al componente.

<?php 
abstract class Decorator extends IComponent 
{ 
    //public function getDescription() { } 
} 
?> 

Credo che questo sia l'unico schema in cui ciò accade nel catalogo Gang of Four. Decorator semplifica l'aggiunta di proprietà a un oggetto senza modificare l'oggetto. Per un esempio semplice, preciso e chiaro vedere:

http://www.php5dp.com/php-decorator-design-pattern-accessorizing-your-classes/#more-32

Problemi correlati