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

时间:2012-06-02 16:04:19

标签: php design-patterns decorator

这本书说:

  

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

我有一只兔子动物。我希望我的兔子能够拥有爬行动物的皮肤。只是想用爬行动物的皮肤装饰一只普通的兔子。

我有代码。首先,我有抽象类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类中始终只有一个抽象方法。当然,它的工作原理。但是在这里我只是制作了一个非常简单的现实生活示例,并尝试使用Decorator模式,如果不在ReptileSkinDecorator类中实现这些抽象方法,它就无法工作。

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

$reptileSkinRabbit = ReptileSkinDecorator(new Rabbit());

我在reptileSkinRabbit实例中有一个常用的兔子实例,它有自己的方法,而这个实例又有自己的reptileSkinRabbit方法。我有兔子兔子。但我认为我没有这种可能性。

我不理解Decarator模式的正确方法。在我对这种模式的理解中,请你指出我的例子中的任何错误。

谢谢。

3 个答案:

答案 0 :(得分:5)

  

我不知道我是否完全理解你的问题。这就是我读它的方式:

你的装饰师尚未完成。如它所示,它仍然是抽象的和不完整的:

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

答案 1 :(得分:5)

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

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非常糟糕,我确定这段代码有几个问题,但我希望你能理解。

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

答案 2 :(得分:3)

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

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

也许更好的例子是HibernationDecorator。由于您的基类与休眠无关,因此您可能需要制作该装饰器,以便为您的兔子提供休眠功能。这就是Decorator所关注的 - 提供基类或接口契约中没有的附加功能,同时仍然遵守基类合同。