装饰器模式有啥用处?我的例子不起作用

Posted

技术标签:

【中文标题】装饰器模式有啥用处?我的例子不起作用【英文标题】:What is there so useful in the Decorator Pattern? My example doesn't work装饰器模式有什么用处?我的例子不起作用 【发布时间】:2012-06-07 11:54:39 【问题描述】:

书上说:

装饰器模式可以用来扩展(装饰) 某个对象的功能

我有一只兔子。例如,我希望我的兔子有爬行动物的皮肤。只想用爬行动物的皮肤装饰一只普通的兔子。

我有密码。首先,我有抽象类Animal,其中包含任何动物共有的所有内容:

abstract class Animal 
    abstract public function setSleep($hours);
    abstract public function setEat($food);
    abstract public function getSkinType();

    /* and more methods which for sure will be implemented in any concrete animal */
 

我为我的兔子创建课程:

class Rabbit extends Animal 
    private $rest;
    private $stomach;
    private $skinType = "hair";

    public function setSleep($hours) 
        $this->rest    = $hours;
    

    public function setFood($food) 
        $this->stomach = $food;
    

    public function getSkinType() 
        return $this->$skinType;
    


到目前为止一切正常。然后我创建抽象的AnimalDecorator 类,它扩展了Animal

abstract class AnimalDecorator extends Animal 
    protected $animal;

    public function __construct(Animal $animal) 
        $this->animal = $animal;
    

问题来了。注意AnimalDecorator 也从Animal 类中获取所有抽象方法(在这个例子中只有两个,但实际上可以有更多)。

然后我创建扩展 AnimalDecorator 的具体 ReptileSkinDecorator 类。它也有来自Animal的两个相同的抽象方法:

class ReptileSkinDecorator extends AnimalDecorator 
    public function getSkinColor() 
        $skin = $this->animal->getSkinType();
        $skin = "reptile";
        return $skin;
    

最后我想用爬行动物的皮肤装饰我的兔子:

$reptileSkinRabbit = ReptileSkinDecorator(new Rabbit());

但我不能这样做,因为我在 ReptileSkinDecorator 类中有两个抽象方法。它们是:

abstract public function setSleep($hours);
abstract public function setEat($food);

因此,我不仅要重新装饰皮肤,还必须重新装饰 setSleep()setEat(); 方法。但我不需要。

在所有书籍示例中,Animal 类中始终只有一个抽象方法。当然它会起作用。但是在这里我只是做了一个非常简单的现实生活示例并尝试使用装饰器模式,如果不在ReptileSkinDecorator 类中实现这些抽象方法,它就无法工作。

这意味着如果我想使用我的示例,我必须创建一个全新的兔子并为其实现自己的 setSleep()setEat() 方法。好吧,就这样吧。但是后来这个全新的兔子有 commont Rabbit 我传递给ReptileSkinDecorator 的实例:

$reptileSkinRabbit = ReptileSkinDecorator(new Rabbit());

我有一个普通的 rabbit 实例,它在 reptileSkinRabbit 实例中有自己的方法,而它又具有自己的 reptileSkinRabbit 方法。我有兔子在兔子。但我想我不必有这种可能。

我没有正确理解 Decarator 模式。在我对这种模式的理解中,请您指出我的示例中的任何错误。

谢谢。

【问题讨论】:

请问是哪本书? 【参考方案1】:

听起来您正试图强制使用不适合问题的特定模式。装饰师通常会聚合一些额外的东西(在头发上添加辫子),而不是像您尝试对皮肤(头发到鳞片)那样完全改变它。

Builder pattern,(您可以指定如何构建对象)可能更适合该问题。在您的情况下,您想构建一个用爬行动物皮肤构建的兔子。 (我来自哪里,而不是爬行动物的皮肤,做一只有角的兔子并称之为jackalope :) 会很有趣:)

我认为在这里使用 Builder(或任何模式)实际上可能是矫枉过正。对于这个特定问题,您绝对必须使用模式吗?不如只定义如下代码并暂时将模式排除在外:

class Animal 
    private $rest;
    private $stomach;
    private $skinType;

    public function setSleep($hours) 
        $this->rest    = $hours;
    

    public function setFood($food) 
        $this->stomach = $food;
    

    public function setSkinType($skin) 
        $this->skinType = $skin;
    

    // define the other getters too

    public function getSkinType() 
        return $this->$skinType;
    


class Rabbit extends Animal 
    public function __construct() 
        $this->rest = "rabbitRest";        // put whatever a rabbit rest is
        $this->stomach = "rabbitStomach";  // put whatever a rabbit stomach is
        $this->skinType = "hair";
    

免责声明:我是一个 c++ 人,我的 php 真的很糟糕,我确定这段代码有几个问题,但我希望你明白。

所以 Rabbit 只会扩展 Animal 并相应地设置其属性。然后,如果有人想要 RabbitReptile,他们可以扩展 Rabbit(可能是矫枉过正)或者只是制作一个 Rabbit 并相应地设置皮肤类型。

【讨论】:

简单。伟大的。我想给+1,但我没票了:/。但保持简单很酷。顺便说一句,您的 PHP 代码看起来还不错。 @hakre,没关系,明天我们会为自己负责 :) 开个玩笑 不,你是对的,谢谢你的提醒。我认为你的回答真的很周到,给人很好的启发。保持好帖子;) @hakre,谢谢,我只是在开玩笑,不过我很感激。我想你也可能欠马克 M 一张票:)【参考方案2】:

我不知道我是否完全理解你的问题。我是这样读的:

您的装饰器尚未完成。正如它所显示的,它仍然是抽象和不完整的:

abstract class AnimalDecorator extends Animal 
    protected $animal;

    public function __construct(Animal $animal) 
        $this->animal = $animal;
    

如果你想让它更容易工作,你也应该装饰所有的方法,所以当你从AnimalDecorator扩展时你不需要这样做。这里是众多方法中的两种示例:

abstract class AnimalDecorator extends Animal 

    ...

    public function getSkinColor() 
        return $this->animal->getSkinColor();
    

    public function setSleep($hours) 
        $this->animal->setSleep($hours);
    


    ...

委托所有方法后,您可以在具体装饰器中覆盖您想要的方法:

class ReptileSkinDecorator extends AnimalDecorator 
    public function getSkinColor() 
        return "reptile";
    

【讨论】:

我正打算写同样的答案。我们可以分享一下要点吗? ;-)(不明白为什么它没有更多的赞成票)【参考方案3】:

装饰器模式必须将其基类或实现的接口中定义的所有成员委托给它正在扩展的对象。换句话说,ReptileSkinDecorator 的 setSleep、setEat 和 getSkinType 方法必须调用 Rabbit(或您选择用爬行动物皮肤装饰的任何其他动物后代)的 setSleep 和 setEat 方法。

另外,我刚刚注意到,在您的示例中,您正在尝试更改 Animal 基类中定义的方法的使用(尝试定义皮肤类型,我假设您希望它是不可变的)。您不应该使用装饰器模式真正做到这一点(隐藏对象的不可变属性),因为如果对象在装饰器范围之外可用,它将提供不同的皮肤类型(如您所定义的那样)。装饰器通常不会更改它们正在装饰的对象的现有属性。

也许一个更好的例子是 HibernationDecorator。由于您的基类与休眠没有任何关系,您可能希望制作该装饰器,以便您可以为您的兔子提供休眠功能。这就是装饰器的意义所在 - 提供基类或接口契约中不存在的附加功能,同时仍遵守基类的契约。

【讨论】:

今天不能再投票了,这是我试图在我的回答中用代码描述的更好的措辞。所以从字面上看+1。 +1 我认为这里真正的问题是他试图强制使用不适合问题的特定模式。 我认为 OP 的示例只是试图找到一种使用该模式的方法。鉴于 Green 不了解该模式的目的,因此选择的示例并不真正适合该模式也就不足为奇了。只是我的解释,

以上是关于装饰器模式有啥用处?我的例子不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Python 装饰器和装饰器模式有啥区别?

设计模式之-装饰器模式

装饰器模式及JAVA IO流例子★★★☆☆

装饰器模式与继承的例子

javascript装饰器模式

java设计模式之装饰器模式以及在java中作用