设计模式:基于《Head First 设计模式》的学习之路(四)--装饰者模式

一、概述:什么是装饰者模式

装饰者模式(Decorator Pattern)是一种结构型设计模式,允许在不改变原有对象结构的情况下动态扩展对象功能。它是继承关系的替代方案,通过包装(wrapper)对象来扩展原有对象的行为。装饰对象与原始对象共享接口,通过持有引用在请求转发前后实现功能增强,常用于需运行时灵活扩展功能的场景。

划重点:继承关系的替代方案

二、问题的提出

星巴兹咖啡馆提供的所有饮料都继承自同一个叫做Beverage(饮料)的抽象类,如下所示:

购买咖啡时,也可以要求在其中加入各种调料,例如,蒸奶(Steamed Milk),豆浆(Soy),摩卡(Mocha)或者其他。

不同调料收取不同的价格,所有订单系统必须考虑到这些调料部分。

下面是一些解决问题的尝试:

1.继承方式

(图片来源于《Head First 设计模式》,复制于网络

上面的方法完全使用了继承,包含不同调料的同一种饮料也都独立成一个类(即:同一种饮料只要稍微调料有变化就定义一个新的类)。这简直就是“类爆炸”,很明显,这简直就是制造了一个维护噩梦。如果牛奶的价格上涨怎么办,新增一种调料风味怎么办?

2.利用实例变量和继承

上述方法类太多了,现在不设计这么多的类。利用实例变量和继承就可以追踪这些调料:

先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡。。。。)

这个设计有哪些问题呢?

  • 调料价格的改变会使我们更改现有的代码。
  • 一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。
  • 以后可能会开发出新的饮料。对这些饮料而言,某些调料可能并不适合,但是在这个设计方式中,子类仍将机继承那些不合适的方法。
  • 万一客户想要双倍摩卡咖啡呢,怎么办?

通过分析可知,上述两种使用继承的方法虽然能解决目前的问题,但是从未来的可能的变化来看的话就显得不合适了。

下面,就从本文的主题“装饰者模式”来解决这个问题。

三、装饰者模式

设计原则:类应该对扩展开放,对修改关闭。

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可以搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。

虽然似乎有点矛盾,但是确实有一些技术可以允许在不直接修改代码的情况下对其进行扩展。

在选择需要被扩展的代码部分时要小心。每个比方都是以开放-关闭原则是一种浪费,也没必要,还会导致代码变得复杂且难以理解。

1.认识装饰者模式

我们已经了解使用继承无法完全解决问题,我们遇到的问题有:类数量爆炸、设计死板,以及基类加入的新功能并不适用于所有的子类。

所以,在这里要才采用不一样的做法:我们要以饮料为主题,然后在运行时以调料来“装饰”饮料。比方说,比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的就是:

  1. 拿一个深焙咖啡(DarkRoast)对象
  2. 以摩卡(Mocha)对象装饰它
  3. 以奶泡(Whip)对象装饰它
  4. 调用cost()方法,并依赖委托(delegate)将调料的价钱加上去

2.以装饰者构造饮料订单

(图片来源于《Head First 设计模式》,复制于网络

现在,该是为顾客计算价钱的时候了。通过调用最外圈的装饰者(Whip)的cost()就可以办得到:

这是到目前我们知道的一切:

  • 装饰者和被装饰者对象有相同的超类型。
  • 你可以用一个或多个装饰者包装一个对象。
  • 既然装饰者和被装饰者有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
  • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
  • 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

3.正式定义装饰者模式

再来回顾下装饰者模式的说明:动态的将责任附加到对象是。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

 

下面是装饰者模式的类图:

 下面将此结构嵌套在饮料上:

思考:调料装饰者除了实现cost()之外,为什么还需要实现getDescription()??如何实现呢?继续往后看 ......

看到这里,是不是在继承和组合之间,观念有一些混淆?看看类图,CondimentDecorator扩展自Beverage,这用到了继承,不是吗?

的确如此,但这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方。在这里,继承的目的是“类型匹配”,而不是利用继承获得“行为”。

当我们将装饰者与组件组合时,就是在加入新的行为。所得到的新行为,并不是继承自超类,而是由组合对象得来的。

在饮料类结构中,继承Beverage抽象类是为了有正确的类型,而不是继承行为。行为来自装饰者(如Milk、Mocha等)和基础组件(如HouseBlend、DarkRoast、Decaf等这些具体的组件),或与其他装饰者之间的组合关系。

因为使用对象组合,可以把所有饮料和调料更有弹性的加以混合与匹配,非常方便(可以实现饮料与调料的随意任意组合)。

如果只是依赖继承,那么类的行为只能在编译时动态的决定。换句话说就是,如果行为不是来自超类,就是来自子类覆盖后的版本。反之,利用组合,可以把装饰者混合着用………而且是在“运行时”。

我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,还得修改现有的代码。

四、把设计用代码实现

该是把设计变成真正的代码的时候了!

1.饮料代码

先从Beverage类开始:

class Beverage//组件类
{
public:
    virtual std::string getDescription()
    {
        return description;
    }

    void setDescription(const std::string &desc){description=desc;}
    virtual ~Beverage()=default;
    virtual double cost()=0;

private:
    std::string description = "Unknown Beverage";
};

Beverage很简单,接下来实现Condimeng(调料)抽象类,也就是装饰者类吧:

class CondimentDecorator:public Beverage //装饰者类
{
public:
    std::string getDescription() override =0 ;
    ~CondimentDecorator()override=default;
};

现在,已经有了基类,开始写饮料的代码。先从浓缩咖啡(Espresso)开始。记得我们需要为具体的饮料设置描述,而且必须实现抽象类的cost()方法:

class Espresso final :public Beverage
{
    public:
    Espresso()
    {
        setDescription("Espresso");//构造时添加描述
    }

    double cost() override//返回基础饮料的价格
    {
        return 1.99;
    }
};

HouseBlend也一样的实现方式:

class HouseBlend final :public Beverage
{
public:
    HouseBlend()
    {
        setDescription("House Blend coffee");//构造时添加描述
    }

    double cost() override//返回基础饮料的价格
    {
        return .89;
    }
};

余下的两种饮料(DarkRoast和Decat)做法都一样,就不继续实现了。

2.调料代码 

结合饮料的类图,我们已经完成了抽象组件(Beverage),还有了具体的组件(饮料,如Espresso、HouseBlend等),也有了抽象装饰者(CondimentDecorator)。下面,我们来实现具体装饰者,也就是调料。

这里只展示Mocha(摩卡)和豆浆(Soy)两种调料,其他的都类似:

//具体装饰者,调料  摩卡
class Mocha final:public CondimentDecorator //摩卡是一个装饰者,所以扩展自CondimentDecorator
{
public:
    Mocha(Beverage *beverage)
    {
        this->beverage = beverage;
    }

    std::string getDescription() override
    {
        return beverage->getDescription()+",Mocha";
    }

    double cost() override
    {
        return beverage->cost()+.20;
    }

    ~Mocha()override
    {
        delete beverage;
        beverage=nullptr;
    }
private:
    Beverage *beverage;
};

//具体装饰者,调料  豆浆
class Soy final:public CondimentDecorator //豆浆是一个装饰者,所以扩展自CondimentDecorator
{
public:
    Soy(Beverage *beverage)
    {
        this->beverage = beverage;
    }

    std::string getDescription() override
    {
        return beverage->getDescription()+",Soy";
    }

    double cost() override
    {
        return beverage->cost()+.10;
    }

    ~Soy()override
    {
        delete beverage;
        beverage=nullptr;
    }
private:
    Beverage *beverage;
};

3.供应完整饮料

现在让我们看看利用装饰者模式设计出的灵活的系统是多么神奇了。我们可以写一些测试程序:

int main()
{
   // std::cout<<"订购一杯Espresso,不需要任何调料:"<<std::endl;
    Beverage *beverage = new Espresso();
    auto des = beverage->getDescription();
    std::cout<<des<<" cost:"<<beverage->cost()<<std::endl;
    delete beverage;
    beverage=nullptr;

    //std::cout<<"订购一杯HouseBlend,加2份摩卡,1份豆浆:"<<std::endl;
    Beverage *beverage2 = new HouseBlend();
    beverage2 = new Mocha(beverage2);
    beverage2 = new Mocha(beverage2);
    beverage2 = new Soy(beverage2);
    des = beverage2->getDescription();
    std::cout<<des<<" cost:"<<beverage2->cost()<<std::endl;
    delete beverage2;//删除最外层装饰对象(会触发链式删除)
    beverage2=nullptr;

    //你可以在这里随意给饮料添加调料,即饮料可以与各种调料随意组合  (装饰者装饰被装饰者)
    return 0;
}

4.完整的代码

下面给出完整的代码:

尝试则运行这个程序,你会体会到装饰者模式有多么灵活。

#include<iostream>

class Beverage//组件类
{
public:
    virtual std::string getDescription()
    {
        return description;
    }

    void setDescription(const std::string &desc){description=desc;}
    virtual ~Beverage()=default;
    virtual double cost()=0;

private:
    std::string description = "Unknown Beverage";

};

class CondimentDecorator:public Beverage //装饰者类
{
public:
    std::string getDescription() override =0 ;
    ~CondimentDecorator()override=default;
};

//组件的具体实现:具体的饮料
class Espresso final :public Beverage
{
public:
    Espresso()
    {
        setDescription("Espresso");//构造时添加描述
    }

    double cost() override//返回基础饮料的价格
    {
        return 1.99;
    }
};

class HouseBlend final :public Beverage
{
public:
    HouseBlend()
    {
        setDescription("House Blend coffee");//构造时添加描述
    }

    double cost() override//返回基础饮料的价格
    {
        return .89;
    }
};

//具体装饰者,即调料
class Mocha final:public CondimentDecorator //摩卡是一个装饰者,所以扩展自CondimentDecorator
{
public:
    Mocha(Beverage *beverage)
    {
        this->beverage = beverage;
    }

    std::string getDescription() override
    {
        return beverage->getDescription()+",Mocha";
    }

    double cost() override
    {
        return beverage->cost()+.20;
    }

    ~Mocha()override
    {
        delete beverage;
        beverage=nullptr;
    }
private:
    Beverage *beverage;
};

class Soy final:public CondimentDecorator //豆浆是一个装饰者,所以扩展自CondimentDecorator
{
public:
    Soy(Beverage *beverage)
    {
        this->beverage = beverage;
    }

    std::string getDescription() override
    {
        return beverage->getDescription()+",Soy";
    }

    double cost() override
    {
        return beverage->cost()+.10;
    }

    ~Soy()override
    {
        delete beverage;
        beverage=nullptr;
    }
private:
    Beverage *beverage;
};

int main()
{
   // std::cout<<"订购一杯Espresso,不需要任何调料:"<<std::endl;
    Beverage *beverage = new Espresso();
    auto des = beverage->getDescription();
    std::cout<<des<<" cost:"<<beverage->cost()<<std::endl;
    delete beverage;
    beverage=nullptr;

    //std::cout<<"订购一杯HouseBlend,加2份摩卡,1份豆浆:"<<std::endl;
    Beverage *beverage2 = new HouseBlend();
    beverage2 = new Mocha(beverage2);
    beverage2 = new Mocha(beverage2);
    beverage2 = new Soy(beverage2);
    des = beverage2->getDescription();
    std::cout<<des<<" cost:"<<beverage2->cost()<<std::endl;
    delete beverage2;//删除最外层装饰对象(会触发链式删除)
    beverage2=nullptr;

    //你可以在这里随意给饮料添加调料,即饮料可以与各种调料随意组合  (装饰者装饰被装饰者)
    return 0;
}

自己尝试将饮料与不同的调料随意组合,体验下装饰者模式的魅力吧~~~

(=============================================================)

以上为装饰者模式,希望对大家有帮助。

感谢Header first 设计模式这本书,写的真棒!后面会继续整理。。。

end with 6698字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三零散人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值