【问题标题】:How to implement a decorator in PHP?如何在 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() {
        // ...
    }
}

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

【问题讨论】:

标签: php oop decorator


【解决方案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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-08
    • 2017-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-29
    • 2011-09-15
    • 2015-06-28
    相关资源
    最近更新 更多