设计模式之装饰模式(修饰模式)

装饰模式也叫修饰模式,维基上对修饰模式的描述是:

修饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

本文将以一个具体的例子说明为什么装饰模式比生成子类的方式更为灵活。

假设有一款城堡游戏,每个城堡有它的价值,我们有普通城堡,拥有钻石的城堡,被污染的城堡,假设普通的城堡价值为 10,拥有钻石的城堡比普通的城堡价值多 5,被污染的城堡比普通的城堡价值少 6,因此我们建了如下的类:

<?php
/**
 * 公用接口,所有物品都是有价值的
 */
abstract class Stuff
{
    abstract function getWealth();
}

class Castle extends Stuff
{
    public function getWealth()
    {
        return 10;
    }
}

class DiamondCastle extends Castle
{
    public function getWealth()
    {
        return parent::getWealth() + 5;
    }
}

class PollutedCastle extends Castle
{
    public function getWealth()
    {
        return parent::getWealth() - 6;
    }
}

看起来还不错,我们可以很方便获得钻石城堡对象也可以获得被污染的城堡对象,但是如果我们想获得即拥有钻石又被污染了的城堡对象该怎么做呢。当然可以新建一个类 DiamondPollutedCastle,但实现起来并不优雅,尤其是当后期又增加一些带有不同属性的城堡时,这种组合会更加的多,显然为每一种组合都创建一个类会使得整个系统类过多。这个时候使用装饰模式就比较合适了。

装饰模式使用组合和委托而不是只使用集成来解决功能变化的问题。Decorator 类会持有另外一个类的实例。Decorator 对象会实现与被调用对象的方法的队友的泪方法。用这种方法可以在运行是创建一系列的 Decorator 对象(《PHP 面向对象模式与实践》p167)。

下面是使用装饰模式重写的城堡游戏类:

<?php
/**
 * 公用接口,所有物品都是有价值的
 */
abstract class Stuff
{
    abstract function getWealth();
}

class Castle extends Stuff
{
    public function getWealth()
    {
        return 10;
    }
}

abstract class Decorator extends Stuff
{
    /**
     * @var Stuff
     */
    protected $stuff;
    public function __construct(Stuff $stuff)
    {
        $this->stuff = $stuff;
    }
}

class DiamondDecorator extends Decorator
{
    public function getWealth()
    {
        return $this->stuff->getWealth() + 5;
    }
}

class PollutedDecorator extends Decorator
{
    public function getWealth()
    {
        return $this->stuff->getWealth() - 6;
    }
}

我们这样来实例化城堡对象:

<?php
// 普通城堡
$generalCastle = new Castle();
// 钻石城堡
$diamondCastle = new DiamondDecorator(new Castle());
// 被污染的城堡
$pollutedCastle = new PollutedDecorator(new Castle());
// 被污染的钻石城堡
$diamondPollutedCastle = new PollutedDecorator(new DiamondDecorator(new Castle()));

通过像这样使用组合和委托,可以在运行时轻松地合并对象,对于初始化类调用的代码来说,并不需要内部是如何合并的,因为每个 Decorator 类都有 getWealth() 方法,所以无论是一个装饰器对象还是真正的 Castle 对象,调用端都可以使用 getWealth() 方法来获取一个装饰器城堡或者普通城堡的价值。