如何在PHP中实现装饰器?

omg*_*omg 31 php oop decorator

假设有一个名为" Class_A" 的类,它有一个名为" func" 的成员函数.

我希望" func"通过包装Class_A在装饰器类中来做一些额外的工作.

$worker = new Decorator(new Original());
Run Code Online (Sandbox Code Playgroud)

有人能举个例子吗?我从未在PHP上使用过OO.

以下版本是对的吗?

class Decorator
{
    protected $jobs2do;

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

    public function do() {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码旨在为数组添加一些额外的工作.

cat*_*ave 43

我建议您还为装饰器和要装饰的对象创建统一的接口(甚至是抽象基类).

要继续上面的示例,您可以使用以下内容:

interface IDecoratedText
{
    public function __toString();
}
Run Code Online (Sandbox Code Playgroud)

然后当然修改两者 TextLeetText实现接口.

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

}
Run Code Online (Sandbox Code Playgroud)

为什么要使用界面?

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

  • 该接口本身与类型安全无关.它有助于调试并提供清晰度.调试示例:您将获得"对象未实现IDecoratedText接口",而不是更通用的"方法不存在"错误.更好的设计:1.这个设计还清楚地与未来的开发人员沟通(没有多余的评论)这个装饰器工作的对象是什么类型(而不是通用的$ text属性).2.界面清楚地表明未来对象(装饰器或"装饰")需要实现哪些方法. (31认同)
  • 还有IDE(例如PhpStorm)提供语义检查.通过向变量添加类型信息(例如IDecoratedText),PhpStorm将指出您是否正在调用该类型提供的方法. (3认同)
  • 我没有看到这里使用接口的好处 (2认同)

sou*_*rge 34

这很容易,特别是在像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
Run Code Online (Sandbox Code Playgroud)

您也可以查看维基百科文章.

  • 对于`Decorator`,你不应该直接从`LeetText`调用任何`Text`方法吗?它应该添加您的功能,但您的示例也会消除功能.例如,如果你在`Text`上有一个`reverse()`方法,你会怎么从`$ text`调用它?我认为您需要使用`__call()`并将方法调用传递回`protected $ text`,以使其成为`Decorator`. (3认同)

Dar*_*ous 23

这些答案都没有Decorator恰当而优雅地实现.mrmonkington的答案很接近,但你不需要使用反射来Decorator在PHP中实现模式.在另一个主题中,@ Gordon展示了如何使用装饰器来记录SOAP活动.他是这样做的:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

他稍微修改了一下你可以将所需的功能传递给构造函数:

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);
    }   
}
Run Code Online (Sandbox Code Playgroud)

  • 您应该改进__call()方法以支持实现fluent-api模式的对象.类似`$ result = call_user_func_array(array($ this-> o,$ method),$ args); return $ result === $ this-> o?$ this:$ result;` (5认同)

小智 6

我想使用装饰来鼓励同事更多地使用缓存,并且受到使用PHP反射实验的漂亮Python语法的启发,以伪造这种语言功能(我必须强调'假').这是一种有用的方法.这是一个例子:

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;
}
Run Code Online (Sandbox Code Playgroud)

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