如何在 PHP 中实现装饰器?

Posted

技术标签:

【中文标题】如何在 PHP 中实现装饰器?【英文标题】:How to implement a decorator in PHP? 【发布时间】:2010-10-31 05:04:27 【问题描述】:

假设有一个名为“Class_A”的类,它有一个名为“func”的成员函数。

我希望“func”通过将 Class_A 包装在装饰器类中来做一些额外的工作。

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

谁能举个例子?我从来没有在 php 中使用过 OO。

下面的版本对吗?

class Decorator

    protected $jobs2do;

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

    public function do() 
        // ...
    

上面的代码打算给数组做一些额外的工作。

【问题讨论】:

你应该初始化你的变量:protected $jobs2do = array(); 但这仍然不是标准版本,也许应该使用'call_user_func'之类的东西 哦,没看到!是的,当然,在 PHP 5.3 之前没有闭包。你绝对应该看看 PHP 中的回调:us.php.net/call-user-func 我写了这个装饰器摘要来支持灵活的装饰器:gist.github.com/CMCDragonkai/6896642 派生自jrgns.net/decorator-pattern-implemented-properly-in-php/… sourcemaking.com/design_patterns/decorator/php 符合要求。会回答,但我不想只是复制和粘贴他的作品。 【参考方案1】:

我建议你也为装饰器和你想要装饰的对象创建一个统一的接口(甚至是抽象基类)。

如果你可以有类似的东西,继续上面的例子:

interface IDecoratedText

    public function __toString();

那么当然修改bothTextLeetText来实现接口。

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());
    


为什么要使用界面?

因为您可以添加任意数量的装饰器,并确保每个装饰器(或要装饰的对象)都具有所有必需的功能。

【讨论】:

这就是为什么我说它在 PHP 中特别容易:你不需要这些东西 :) 如果你真的想要类型安全,你应该使用另一种语言。 我没有看到在这里使用界面的好处 谢谢,但是如何将新成员函数堆积到数组中?似乎我当前的版本会引起警告。 接口本身与类型安全无关。它有助于调试并提供清晰度。调试示例:您将收到“对象未实现 IDecoratedText 接口”,而不是更通用的“方法不存在”错误。更好的设计: 1. 这种设计还清楚地向未来的开发人员(没有无关的 cmets)传达了这个装饰器在什么类型的对象上工作(而不是通用的 $text 属性)。 2. 接口明确了未来对象(装饰器或“装饰者”)需要实现哪些方法。 还有提供语义检查的 IDE(例如 PhpStorm)。通过将类型信息(例如 IDecoratedText)添加到变量中,PhpStorm 将指出您是否正在调用该类型提供的方法。【参考方案2】:

这很容易,尤其是在 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

您可能也想看看wikipedia article。

【讨论】:

谢谢,但是如何将新成员函数堆积到数组中?似乎我当前的版本会引起警告。 为什么 $string 和 $text 不是私有的? 公共函数 __construct($text) 不清楚。 $text 是什么意思?怎么看一眼就知道里面应该放什么?犯错太容易了。 抱歉,我好像忘记记录我的代码 sn-p。无论如何,这里的标准是什么?我在哪里可以找到这个论坛的编码指南?我们使用 doxygen 风格的 doc cmets 还是 javadoc?还是针对不同的语言有不同的指导方针? 对于Decorator,难道你不能直接从LeetText 调用任何Text 方法吗?它应该添加您的功能,但您的示例也带走了功能。例如。如果你在Text 上有一个reverse() 方法,你会如何从$text 调用它?我认为您需要使用__call() 并将方法调用传回protected $text 才能成为Decorator【参考方案3】:

这些答案都没有正确而优雅地实现Decorator。 mrmonkington 的答案很接近,但您不需要使用反射来制定 PHP 中的 Decorator 模式。在另一个线程中,@Gordon shows how to use a decorator for logging SOAP activity。他是这样做的:

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);
    

他是一个轻微的修改,您可以将所需的功能传递给构造函数:

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);
       

【讨论】:

您应该改进 __call() 方法以支持实现 fluent-api 模式的对象。类似$result = call_user_func_array(array($this->o, $method), $args); return $result === $this->o ? $this : $result; 请解释一下$function_name的用法和工作流程【参考方案4】:

我想用装饰来鼓励同事更多地使用缓存,并受到漂亮的 Python 语法的启发,用 PHP 反射来伪造这个语言特性(我必须强调“假”)。这是一个有用的方法。这是一个例子:

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;

这里有更好的缓存示例:https://github.com/gamernetwork/EggCup

【讨论】:

这更像是一种代理模式!!【参考方案5】:

装饰器的一个关键和独特的特性是一个抽象类扩展另一个抽象类。装饰器参与者既是组件参与者的包装器,又是组件参与者的扩展。

<?php
abstract class Decorator extends IComponent

    //public function getDescription()  

?>

我相信这是四人帮目录中唯一出现这种情况的模式。装饰器可以很容易地在不更改对象的情况下向对象添加属性。有关简单、准确和清晰的示例,请参阅:

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

【讨论】:

以上是关于如何在 PHP 中实现装饰器?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flask 中实现登录所需的装饰器

在 PHP 中实现方法结果缓存的装饰器模式的最佳方法

在 PHP 中构造装饰器

在 Python 中实现装饰器模式

Python 中实现装饰器时使用 @functools.wraps 的理由

是否有用于在 Django 中实现视图模型装饰器 ala Draper 的库?