zoukankan      html  css  js  c++  java
  • 【设计模式

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

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

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

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


    一、定义与特点

    装饰(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++装饰模式


  • 相关阅读:
    宿主机( win 7 系统) ping 虚拟机VMware( cent os 6.6 ) 出现“请求超时”或者“无法访问目标主机”的解决方法
    Java实现 LeetCode 23 合并K个排序链表
    Java实现 LeetCode 23 合并K个排序链表
    Java实现 LeetCode 23 合并K个排序链表
    Java实现 LeetCode 22 括号生成
    Java实现 LeetCode 22 括号生成
    Java实现 LeetCode 22 括号生成
    Java实现 LeetCode 21 合并两个有序链表
    Java实现 LeetCode 21 合并两个有序链表
    Java实现 LeetCode 21 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/13679907.html
Copyright © 2011-2022 走看看