2

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

这是一个控制台应用程序,可为命令行输出添加颜色。装饰师添加颜色。ClientClass中的条件是 如果发生错误则用红色文本装饰,如果没有发生错误则用绿色文本装饰。因此,颜色装饰器具有只有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(仅在装饰器类中可用)才能调用可用于所有扩展 MyAceBaseClass 的类的抽象方法 doYourThing()。这感觉不对。

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

我该如何解决这个问题?

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

一种可能性是为 MyAceBaseClassDecoratorA 提供一个接口,但是我首先违背了使用装饰器的目的。

4

3 回答 3

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;
}
于 2012-08-25T18:15:05.127 回答
0

是的,如果您的构造函数采用 MyAceBaseClass,它不应该在其上调用在其后代类之一中声明的方法。如果 $aceObject 类将始终是一个装饰器,那么为它创建一个接口,并且只调用在接口中声明的方法,这就是接口的用途。将其“归结为”一种干净的方法会很有帮助,并将知道要调用哪些特定方法的责任委托给装饰器。

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

于 2012-08-25T17:32:29.037 回答
0

如果你想让它变得简单,你可以根据条件(是否要添加颜色)在 doYourThing 方法中使用 decoratorFunctionA() 和 decoratorFunctionB()。这样,您的合同将保持不变,您可以获得所需的功能。

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

有关更多详细信息,您可以参考:

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

于 2012-08-27T07:01:45.363 回答