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


What is there so useful in the Decorator Pattern? My example doesn't work

书中说:

装饰

器图案可用于扩展(装饰)的 特定对象的功能

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

我有代码。首先,我有抽象类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 类中获取所有抽象方法(在此示例中只有两个,但实际上可以有更多)。

然后我创建具体的ReptileSkinDecorator类来扩展AnimalDecorator。它还具有与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()方法。好吧,顺其自然。但是这只全新的兔子有我传递给ReptileSkinDecorator的普通Rabbit实例:

$reptileSkinRabbit = ReptileSkinDecorator(new Rabbit());

我有一个常见的兔子实例,在爬行动物SkinRabbit实例中有自己的方法,而它又有自己的爬行动物SkinRabbit方法。我有兔子。但我想我不必有这种可能性。

我不理解正确的装饰器模式。请您指出我示例中的任何错误,以及我对这种模式的理解。

谢谢。

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

生成器模式(通过指定您希望如何构建对象)可能更适合问题。在你的情况下,你想建造一只用爬行动物皮肤建造的兔子。(我来自,而不是爬行动物的皮肤,做一只有角的兔子并称它为豺狼:)

我认为在这里使用 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 真的很糟糕,我确定这段代码有几个问题,但我希望你明白这个想法。

因此,兔子只会扩展动物并相应地设置其属性。然后,如果有人想要兔子爬行动物,他们可以延长兔子(可能是矫枉过正),或者只是做一只兔子并相应地设置皮肤类型。

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

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

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";
    }
}

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

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

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