装饰模式问题

时间:2012-08-25 17:16:08

标签: php oop design-patterns dependency-injection decorator

我正在使用装饰器模式将样式和选项添加到输出流(控制台)。

  

这是一个控制台应用程序,可以为命令行输出添加颜色。该   装饰者添加颜色。 ClientClass 中的条件是   如果发生错误,则用红色文字装饰,如果没有错误则装饰,然后使用绿色文字进行装饰。颜色   因此,decorator只有 ClientClass 的可选调用   可以知道。

     

* OP对其中一个答案的评论副本

我遇到的问题是:我通过构造函数将我的装饰器传递给一个类。但是装饰对象要求我调用两个不在基类中的方法,即被装饰的方法。

这不好吗?难道我只能调用所有装饰类中可用的方法吗?

举个例子:

<?php

abstract class MyAceBaseClass
{ 
    abstract function doYourThing();
}


class MyAceBaseClassDecoratorA extends MyAceBaseClass
{ 
    protected $aceBaseClass;

    protected $amount = 0;

    public function __construct($aceBaseClass)
    { 
         $this->_aceBaseClass = $aceBaseClass;
    }

    public function doYourThing()
    {
        $result = $aceBaseClass + $this->amount;
        return $result;
    }

    public function decoratorFunctionA()
    {
        $this->amount = 10;
    }

    public function decoratorFunctionB()
    {
        //----
    }
}

class ClientClass
{ 
    private $aceObject;

    public function __construct(MyAceBaseClass $aceObject) 
    {
         $this->aceObject = $aceObject;       
    }

    public function run()
    {
         if ($someCondition) {
             $this->aceObject->decoratorFunctionA();
             $this->aceObject->decoratorFunctionB();
         }

         $result = $this->aceObject->doYourThing();

         echo $result;
    }
}

正如您所看到的,客户端类需要调用decoratorFunctionA和decoratorFunctionB(它们只能在decorator类中使用),然后才能调用抽象方法doYourThing(),这些方法可用于扩展MyAceBaseClass的所有类。这感觉不对。

我认为装饰器的实现是正确的,但现在我遇到了其他问题

我该如何解决这个问题?

客户端类可能不需要同时调用decoratorFunctionA()和decoratorFunctionB(),它可能只需要1或无,所以我不能在doYourThing调用中自动调用它们。

一种可能性是为MyAceBaseClassDecoratorA设置一个接口,但是我首先打败了使用装饰器的目的。

3 个答案:

答案 0 :(得分:1)

你不能调用你期望的接口未指定的方法:MyAceBaseClass(正如@ctrahey指出的那样)。

另外,如果我正确地阅读它,你似乎正在把事情搞混了。 在这种情况下,谁在装饰谁?

如果装饰器模式的目的是在没有子类化的情况下向现有对象添加功能(换句话说,不说对象“是”),装饰器必须具有受保护的属性与装饰物体。看起来你的ClientClass正在装饰你所谓的MyAceBaseClassDecoratorA

IMO,它应该是这样的:

// Concrete object
class MyObject implements MyObjectInterface {
   public function doSomething() {
       return 1;
   }
}

// Your abstract decorator
abstract class MyObjectDecorator implements MyObjectInterface {
    protected $myObject;

    public function __construct(MyObjectInterface $object) {
        $this->myObject = $object;
    }

    public function doSomething() {
        return $this->myObject->doSomething();
    }
}

// Your concrete decorator
class MyConcreteObjectDecorator extends MyObjectDecorator {
    public function doSomething() {
        $value = $this->myObject->doSomething();
        return $value + 10;
    }
}

<强>编辑:

至于你在评论中的解释,这是你应该做的:

class ClientClass {
    // ...
    public function run() {
        $this->aceObject->doYourThing(); // and that's all
    }
    // ...
}

因此,您不会像在$this->aceObject->decoratorFunctionA();中那样在装饰器外召唤$this->aceObject->decoratorFunctionB();ClientClass

相反,您应该在调用doYourThing()方法之前为装饰对象提供注入$ClientClass->aceObject->doYourThing()方法的功能。

如果出现错误,那么您将使用RedTextDecorator装饰它,如果一切顺利,则用GreenTextDecorator装饰它。

public function run()
{
     if ($error) {
         $this->aceObject = new RedTextDecorator($this->aceObject);
     } elseif($success) {
         $this->aceObject = new GreenTextDecorator($this->aceObject);
     }

     $result = $this->aceObject->doYourThing();

     echo $result;
}

答案 1 :(得分:0)

是的,如果你的构造函数接受了MyAceBaseClass,那么它永远不应该在它上面调用在其中一个后代类中声明的方法。如果$ aceObject类总是是装饰器,那么为它创建一个接口,并且只调用接口上声明的方法,这就是接口的用途。将一个干净的方法“煮沸”可能会有所帮助,并且委托知道调用装饰器的具体方法的责任。

然而,我可能会提到,我更喜欢我的装饰者更多地了解他们的主题而不是主题知道它的装饰者。一个对象与它的装饰器的关系应尽可能松散耦合,希望它只是知道它可以被装饰(即实现一个“可装饰的”接口或类似的东西,如实现感兴趣的装饰者可以观察到的观察机制) 。但是,装饰者所做的知道它的主题应该只通过一个界面。

答案 2 :(得分:0)

如果你想简化你可以在doYourThing方法中使用decoratorFunctionA()和decoratorFunctionB()基于条件(是否要添加颜色)。通过这种方式,您的合同将保持不变,并且您可以获得所需的功能。

如果你想让它更清晰,那么也许你可以使用两个不同的装饰器。

有关详细信息,请参阅:

http://en.wikipedia.org/wiki/Decorator_pattern