【设计模式 - 结构型模式】4. 装饰模式

在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等,这些都可以使用装饰模式。它的主要作用就是:

  • 增强一个类原有的功能。
  • 为一个类添加新的功能。

并且装饰模式也不会改变原有的类

在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰模式来实现。


一、定义与特点

装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

主要优点有:

  • 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用;
  • 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果;
  • 装饰器模式完全遵守开闭原则。

其主要缺点是:装饰模式会增加许多子类,过度使用会增加程序得复杂性。


二、结构与实现

通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。

如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,就可以在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式。下面来分析其基本结构和实现方法。

装饰模式的结构

装饰模式主要包含以下角色:

  1. 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  2. 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  3. 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  4. 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

装饰模式的结构图如下图所示:
装饰模式的结构图


装饰模式的实现代码如下:

#include <iostream>

using namespace std;

// 抽象构件角色
class Component {
public:
    Component() {}
    virtual void operation() {}
};

// 具体构件角色
class ConcreteComponent: public Component {
public:
    ConcreteComponent() {
        cout << "创建具体构件角色" << endl;
    }
    void operation() {
        cout << "调用具体构件角色的方法operation()" << endl;
    }
};

// 抽象装饰角色
class Decorator: public Component {
public:
    Decorator(Component *component) {
        this->component = component;
    }
    void operation() {
        component->operation();
    }

public:
    Component *component;
};

// 具体装饰角色
class ConcreteDecorator: public Decorator {
public:
    // 关键要加上后面的Decorator(component)
    ConcreteDecorator(Component *component):Decorator(component) { }
    void operation() {
        component->operation();
        addedFunction();
    }
    void addedFunction() {
        cout << "为具体构件角色增加额外的功能addedFunction()" << endl;
    }
};

int main()
{
    Component *com = new ConcreteComponent();
    com->operation();
    cout << "---------------------------------" << endl;
    Component *dec = new ConcreteDecorator(com);
    dec->operation();

    return 0;
}

程序运行结果如下:

创建具体构件角色
调用具体构件角色的方法operation()
---------------------------------
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能addedFunction()

可以看到,ConcreteDecorator 装饰给 ConcreteComponent 类添加了动作 addedFunction。


三、应用实例

3.1 用于增强功能的装饰模式

我们用程序来模拟一下戴上装饰品提高我们颜值的过程:

#include <iostream>

using namespace std;

// 抽象构件角色:颜值接口
class IBeauty {
public:
    IBeauty() {}
    virtual int getBeautyValue() {}
};

// 具体构件角色:Me类
class Me: public IBeauty {
public:
    // @Override
    int getBeautyValue() {
        return 100;
    }
};

// 具体装饰角色:戒指装饰类
class RingDecorator: public IBeauty {
public:
    RingDecorator(IBeauty *me) {
        this->me = me;
    }

    // @Override
    int getBeautyValue() {
        return me->getBeautyValue() + 20;
    }

public:
    IBeauty *me;
};

int main()
{
    IBeauty *me = new Me();
    cout << "我原本的颜值:" << me->getBeautyValue() << endl;

    IBeauty *meWithRing = new RingDecorator(me);
    cout << "戴上了戒指后,我的颜值:" << meWithRing->getBeautyValue() << endl;

    return 0;
}

运行程序,输出如下:

我原本的颜值:100
戴上了戒指后,我的颜值:120

这就是最简单的增强功能的装饰模式。以后我们可以添加更多的装饰类,比如:

// 具体装饰角色:耳环装饰类
class EarringDecorator: public IBeauty {
public:
    EarringDecorator(IBeauty *me) {
        this->me = me;
    }

    // @Override
    int getBeautyValue() {
        return me->getBeautyValue() + 50;
    }

private:
    IBeauty *me;
};

// 具体装饰角色:项链装饰类
class NecklaceDecorator: public IBeauty {
public:
    NecklaceDecorator(IBeauty *me) {
        this->me = me;
    }

    // @Override
    int getBeautyValue() {
        return me->getBeautyValue() + 50;
    }

private:
    IBeauty *me;
};

int main()
{
    IBeauty *me = new Me();
    cout << "我原本的颜值:" << me->getBeautyValue() << endl;

    // 随意挑选装饰
    IBeauty *meWithRing = new RingDecorator(me);
    cout << "戴上了戒指后,我的颜值:" << meWithRing->getBeautyValue() << endl;

    // 多次装饰
    IBeauty *meWithManyDecorators = new NecklaceDecorator(new RingDecorator(new EarringDecorator(me)));
    cout << "戴上耳环、戒指、项链后,我的颜值:" << meWithManyDecorators->getBeautyValue() << endl;

    // 任意搭配装饰
    IBeauty *meWithNecklaceAndRing = new NecklaceDecorator(new RingDecorator(me));
    cout << "戴上戒指、项链后,我的颜值:" << meWithNecklaceAndRing->getBeautyValue() << endl;

    return 0;
}

运行程序,输出如下:

我原本的颜值:100
戴上了戒指后,我的颜值:120
戴上耳环、戒指、项链后,我的颜值:220
戴上戒指、项链后,我的颜值:200

可以看到,装饰器也实现了 IBeauty 接口,并且没有添加新的方法,也就是说这里的装饰器仅用于增强功能并不会改变 Me 原有的功能,这种装饰模式称之为透明装饰模式,由于没有改变接口,也没有新增方法,所以透明装饰模式可以无限装饰

装饰模式是继承的一种替代方案。本例如果不使用装饰模式,而是改用继承实现的话,戴着戒指的 Me 需要派生一个子类、戴着项链的 Me 需要派生一个子类、戴着耳环的 Me 需要派生一个子类、戴着戒指 + 项链的需要派生一个子类......各种各样的排列组合会造成类爆炸。而采用了装饰模式就只需要为每个装饰品生成一个装饰类即可,所以说就增加对象功能来说,装饰模式比生成子类实现更为灵活


3.2 用于添加功能的装饰模式

我们用程序来模拟一下房屋装饰粘钩后,新增了挂东西功能的过程:

#include <iostream>

using namespace std;

// 抽象构件角色:颜值接口
class IHouse {
public:
    virtual void live() {}
};

// 具体构件角色:房屋类
class House: public IHouse {
public:
    // @Override
    void live() {
        cout << "房屋原有的功能:居住功能" << endl;
    }
};

// 抽象装饰角色:粘钩装饰器接口
class IStickyHookHouse: public IHouse {
public:
    virtual void hangThings() {}
};

// 具体装饰角色:粘钩装饰类
class StickyHookDecorator: public IStickyHookHouse {
public:
    StickyHookDecorator(IHouse *house) {
        this->house = house;
    }

    // @Override
    void live() {
        house->live();
    }

    // @Override
    void hangThings() {
        cout << "有了粘钩后,新增了挂东西功能" << endl;
    }

private:
    IHouse *house;
};

int main()
{
    IHouse *house = new House();
    house->live();

    // 新增功能
    IStickyHookHouse *stickyHookHouse = new StickyHookDecorator(house);
    stickyHookHouse->live();
    stickyHookHouse->hangThings();

    return 0;
}

运行程序,显示如下:

房屋原有的功能:居住功能
房屋原有的功能:居住功能
有了粘钩后,新增了挂东西功能

这就是用于新增功能的装饰模式。我们在接口中新增了方法:hangThings,然后在装饰器中将 House 类包装起来,之前 House 中的方法仍然调用 house 去执行,也就是说我们并没有修改原有的功能,只是扩展了新的功能,这种模式在装饰模式中称之为半透明装饰模式

为什么叫半透明呢?由于新的接口 IStickyHookHouse 拥有之前 IHouse 不具有的方法,所以我们如果要使用装饰器中添加的功能,就不得不区别对待装饰前的对象和装饰后的对象。也就是说客户端要使用新方法,必须知道具体的装饰类 StickyHookDecorator,所以这个装饰类对客户端来说是可见的、不透明的。而被装饰者不一定要是 House,它可以是实现了 IHouse 接口的任意对象,所以被装饰者对客户端是不可见的、透明的。由于一半透明,一半不透明,所以称之为半透明装饰模式。

我们可以添加更多的装饰器:

// 抽象装饰角色:镜子装饰器的接口
class IMirrorHouse: public IHouse {
public:
    virtual void lookMirror() {}
};

// 具体装饰角色:镜子类
class MirrorDecorator: public IMirrorHouse {
public:
    MirrorDecorator(IHouse *house) {
        this->house = house;
    }

    // @Override
    void live() {
        house->live();
    }

    // @Override
    void lookMirror() {
        cout << "有了镜子后,新增了照镜子功能" << endl;
    }

private:
    IHouse *house;
};

int main()
{
    IHouse *house = new House();
    house->live();

    IMirrorHouse *mirrorHouse = new MirrorDecorator(house);
    mirrorHouse->live();
    mirrorHouse->lookMirror();

    return 0;
}

运行程序,输出如下:

房屋原有的功能:居住功能
房屋原有的功能:居住功能
有了镜子后,新增了照镜子功能

现在我们仿照透明装饰模式的写法,同时添加粘钩和镜子装饰试一试:

int main()
{
    IHouse *house = new House();
    house->live();

    IStickyHookHouse *stickyHookHouse = new StickyHookDecorator(house);
    IMirrorHouse *houseWithStickyHookMirror = new MirrorDecorator(stickyHookHouse);
    houseWithStickyHookMirror->live();
    houseWithStickyHookMirror->hangThings(); // 这里会报错,找不到 hangThings 方法
    houseWithStickyHookMirror->lookMirror();

    return 0;
}

我们会发现,第二次装饰时,无法获得上一次装饰添加的方法。原因很明显,当我们用 IMirrorHouse 装饰器后,接口变为了 IMirrorHouse,这个接口中并没有 hangThings 方法。

那么我们能否让 IMirrorHouse 继承自 IStickyHookHouse,以实现新增两个功能呢?

可以,但那样做的话两个装饰类之间有了依赖关系,那就不是装饰模式了。装饰类不应该存在依赖关系,而应该在原本的类上进行装饰。这就意味着,半透明装饰模式中我们无法多次装饰

只要添加了新功能的装饰模式都称之为 半透明装饰模式,他们都具有不可以多次装饰的特点。仔细理解上文半透明名称的由来就知道了,“透明”指的是我们无需知道被装饰者具体的类,既增强了功能,又添加了新功能的装饰模式仍然具有半透明特性。


参考:

知乎 - 如何学习设计模式? 热门回答

装饰模式(装饰设计模式)详解

菜鸟教程 - 设计模式篇

C++装饰模式


posted @ 2021-02-10 20:08  fengMisaka  阅读(187)  评论(0编辑  收藏  举报