• C++设计模式之装饰者模式(结构型模式)


    学习软件设计,向OO高手迈进!
    设计模式(Design pattern)是软件开发人员在软件开发过程中面临的一般问题的解决方案。
    这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
    是前辈大神们留下的软件设计的"招式"或是"套路"。

    什么是装饰者模式

    动态地给一个对象添加一些额外的职责。就增加功能来说, 装饰者模式相比生成子类更为灵活。

    有时我们希望给某个对象而不是整个类添加一些功能。比如有一个手机,允许你为手机添加特性,
    比如增加挂件、屏幕贴膜等。一种灵活的设计方式是,将手机嵌入到另一对象中,由这个对象完成特性
    的添加,我们称这个嵌入的对象为装饰。这个装饰与它所装饰的组件接口一致,因此它对使用该组件的
    客户透明。

    意图动态地给一个对象添加一些额外的职责
    问题有时我们希望给某个对象而不是整个类添加一些功能。 即动态的为对象添加新的功能
    解决方案采用组合而非单纯继承的方式,扩展一个对象的功能
    实现创建一个原始抽象类和要添加类上的新功能。 让具体类装饰类分别继承自原始抽象类。 装饰类中包含原始抽象类的引用

    UML类图

    在这里插入图片描述

    装饰者模式中的角色:

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

    Version 1.0

    下面我们使用买手机来讲解该模式:

    我们在生活中去买手机,经常会有套餐的例子,比如,我们买一部手机祼机多少钱,外加一个耳机,构
    成的套餐价,多少钱。外加一个保护膜,构成的另外套餐价多少钱,组合套餐呢?
    这分明,是一个变化极为复杂的组合。

    一、定义抽象类

    Phone类:原始抽象类(Component)

    class Phone {
    public:
        virtual int cost() = 0;
    };
    
    • 1
    • 2
    • 3
    • 4

    二、定义具体类

    IPhone类:具体类(ConcreteComponent)

    class IPhone : public Phone {
    public:
        int cost() {
            return 5000;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    三、定义抽象装饰类

    DecoratePhone类:抽象装饰类(Decorator)

    class DecoratePhone : public Phone {
    public:
        DecoratePhone(Phone *ph) : m_Phone(ph) {}
    protected:
        // 子类会用到这个成员, 所以声明为 protected
        Phone *m_Phone;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    四、定义具体装饰类

    ScreenProtectorPhone类:具体装饰类(ConcreteDecoratorA)

    class ScreenProtectorPhone : public DecoratePhone {
    public:
        ScreenProtectorPhone(Phone *ph) : DecoratePhone(ph) {}
        int cost() {
            return 39 + m_Phone->cost();
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    HeadSetPhone类:具体装饰类(ConcreteDecoratorB)

    class HeadSetPhone : public DecoratePhone {
    public:
        HeadSetPhone(Phone *ph) : DecoratePhone(ph) {}
        int cost() {
            return 99 + m_Phone->cost();
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    五、客户端

    int main(int argc, char** argv) {
        // 单买手机 多少钱
        IPhone apple;
        cout << apple.cost() << endl;
    
        // 手机 + 钢化膜 多少钱
        ScreenProtectorPhone sp(&apple);
        cout << sp.cost() << endl;
    
        // 手机 + 钢化膜x2 多少钱
        ScreenProtectorPhone sp2(&sp);
        cout << sp2.cost() << endl;
    
        // 手机 + 钢化膜 + 耳机 多少钱
        HeadSetPhone hp(&sp);
        cout << hp.cost() << endl;
    
        // 手机 + 钢化膜x2 + 耳机x2 多少钱
        Phone *p = new ScreenProtectorPhone(new ScreenProtectorPhone(new HeadSetPhone(new HeadSetPhone(&apple))));
        cout << p->cost() << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    执行结果

    5000
    5039
    5078
    5138
    5276
    
    • 1
    • 2
    • 3
    • 4
    • 5

    优点

    1. 装饰者模式可以带来比继承更加灵活的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。
    2. 装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
    3. 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
    4. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

    缺点

    1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
    2. 装饰者模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
    3. 装饰者模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。

    适用场合

    当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
    不能采用继承的情况主要有两类:

    1. 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
    2. 第二类是因为类定义不能继承(如final类)

    在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
    当对象的功能要求可以动态地添加,也可以再动态地撤销时。

  • 相关阅读:
    8月27日计算机视觉理论学习笔记——图说
    22.10.30补卡 22CCPC桂林站M题
    2022 面试必刷 461 道大厂架构面试真题汇总 + 面经 + 简历模板
    React-native Android 添加音效
    [Python进阶] 操纵鼠标:Pynput
    Linux友人帐之网络编程基础NFS服务器
    【SCAU数据挖掘】数据挖掘期末总复习题库应用题及解析
    android11.0 Launcher3 高端定制之电话和短信未读标记(红圈数字)
    基于探针的分布式追踪工具
    vue2中如何跨组件调用方法涉及路由跳转以及实现方式
  • 原文地址:https://blog.csdn.net/cfl927096306/article/details/126676931