装饰器模式问题 - 如何调用嵌套装饰器方法?

Posted

技术标签:

【中文标题】装饰器模式问题 - 如何调用嵌套装饰器方法?【英文标题】:Decorator pattern issue - how to call nested decorators methods? 【发布时间】:2016-06-11 17:55:56 【问题描述】:

我现在正在研究装饰器模式,这里有一些示例代码(php):

abstract class component

  public function drawShape();



class concreteComponent extends component

   public function drawShape()//code;



class decoratorComponent extends component

  private $component;

  public function __construct($component) $this->component=$component; 

  public function drawShape()

      $this->component->drawShape();

  



class borderDecorator extends decoratorComponent

  public function drawShape()

    $this->drawBorder();
    $this->component->drawShape();

  

  public function setBorder();
  public function drawBorder();



class bgColorDecorator extends decoratorComponent

  public function drawShape()

    $this->drawBgColor();
    $this->component->drawShape();

  

  public function setbgColor();
  public function drawBgColor();


好的,现在:

$test=new concreteComponent();
$border=new borderDecorator($test);
$border->setBorder(10);
$bgColor= new bgColorDecorator($border);
$bgColor->setBgColor(#000);

现在我有一个装饰有 #000 bg 颜色和 10(某个单位)边框的组件。

$bgColor->drawShape(); 

意思是drawBgColor + drawBorder + drawShape 好吧,但是:

如何修改或移除边框??

$bgColor-> ???

bgColor 类不能直接访问边框方法...

谢谢

【问题讨论】:

嗯,我正在看书,你可以把它看成接口,所以我有相同的方法 drawShape(),如果我使用的是装饰组件,我可以称之为不用担心或不是(我说得对吗?)@PeeHaa 如果您的对象图不是太复杂,只需在装饰器发生变化时从头开始重建整个图。但是,如果开销太大,您可能会编写一个了解其他功能或插件的装饰器。类似$test = new ConcreteCmp(); $pluggableCmp = new PluggableDecorator($test); $pluggableCmp.addFeature(new BorderFeature()); $pluggableCmp.addFeature(new BGColorFeature()) 然后如何从 $pluggableCmp 调用 BorderFeature 或 BGColorFeature 的方法来修改它们? @plalx 不确定您的意思... $pluggableCmp 将包含一个内部功能列表。您最常选择最合适的策略来跟踪特征。如果每种类型可能只存在一个,那么也许将它们存储在地图中。您还可以决定一个特征可以通过它的类型和属性来识别,并将其视为一个值。因此,您可以$pluggableCmp.addFeature(new Border(black)) 并执行$pluggableCmp.removeFeature(new Border(black))。修改功能只是将其删除并使用不同的配置重新添加。 【参考方案1】:

据我了解,您已将您的装饰器链接起来,从而忽略了您无法设置/删除边框的 bgColorDecorator 实例。

你应该做的是改变构造的顺序并由borderDecorator部分完成:

$test=new concreteComponent();

$bgColor= new bgColorDecorator($test); // pass test to bgcolor
$bgColor->setBgColor(#000);

$border=new borderDecorator($bgColor); // pass bgcolor to border
$border->setBorder(10);

// You can now set/remove border on $border
// and of course : $border->drawShape();

您的任务似乎是渲染一个对象,因此正确的绘图顺序应该需要更改您的 drawShape 方法以保持顺序背景/形状/边框

$this->drawBgColor();
$this->component->drawShape();
// will become a post-action
$this->component->drawShape();    
$this->drawBgColor();

现在的问题是出于同样的原因,您将无法动态设置背景色。因此,另一种解决方案可能是修改您的 decoratorComponent 接口以包含您需要的内容并在 decoratorComponent 子类中实现它。

编辑双边框情况:

只需将两个borderDecorator链接到一个组件

$cmp = new concreteComponent();

$bDecrtor1 = new borderDecorator($cmp); // 1st border decorator on cmp
$bDecrtor1 ->setBorder(10);

$bDecrtor2=new borderDecorator($bDecrtor1); // 2nd border decorator on 1st one
$bDecrtor2->setBorder(20);

// $bDecrtor2->drawShape();
// You can then use bDecrtor1 or bDecrtor2 to (re)set the border properties
// You can use bDecrtor2 to chain other decorators...

【讨论】:

我现在不需要那个代码,我只是想练习,然后我看不出装饰器如何成为一个灵活的解决方案......(我正在读一本面向 OOP 的书)...装饰器应该允许我动态地添加/删除动作和运行时到一个对象(装饰),但是当我添加多个装饰器时,我在最后一个装饰器之前添加的装饰器变得无法使用......如果我有 10装饰器我不能每次都修改装饰器组件接口来添加一个可能的装饰器动作,这不是模式背后的轻巧灵活的逻辑......@floppy12 @Francesco 从某种意义上说,这是一个灵活的解决方案,您可以将装饰器链接到您的组件以构建更多进化的组件等等,实际上链接的顺序会使相同的源代码产生不同的行为。顺便说一句,你为什么不使用每一个装饰器(不仅仅是最新的)?您可以使用它们中的每一个来(重新)设置所需的属性 如果我想要双边框?如果我使用另一个borderDecorator 覆盖第一个borderDecator,我只能使用一个... @floppty12 @Francesco 您可以链接两个边框装饰器,而无需实现特定的“双边框”装饰器类......这是我正在谈论的灵活性。有关示例,请参阅编辑。 嗯,我仍然想念如何从 $bDecrtor2 istance 更改第一个边框... $bDecrtor2->?? @floppy12【参考方案2】:

您可以做的是实现名为__call() 的magic method(一个包罗万象的方法),以尝试将任何不存在的方法委托给包装的component

class decoratorComponent extends component

  private $component;

  /* ... */

  public function __call( $name, $arguments ) 
    return call_user_func_array( array( $this->component, $name ), $arguments );
  

您只需要构建安全措施即可捕获您尝试调用的方法最终在任何组件中都不存在的情况。

但是,您可能还应该评估是否需要在已经将装饰器方法包装在另一个装饰器中之后调用它们。

【讨论】:

我已经知道 PHP 的“魔术方法”,但这是一种棘手的方法,我希望只用一种逻辑模式(独立于特定的编程语言)来解决这个问题 @Francesco 问题是装饰器模式通常不用于添加可以随机调用的其他方法,而是修改 现有 方法的行为(可能可通过构造函数参数配置)。在您的情况下,这意味着您将通过 borderDecorator 的构造函数传递边框大小。

以上是关于装饰器模式问题 - 如何调用嵌套装饰器方法?的主要内容,如果未能解决你的问题,请参考以下文章

5.初识python装饰器 高阶函数+闭包+函数嵌套=装饰器

装饰器181029

python 装饰器的嵌套调用

装饰器

day04-装饰器

装饰器