设计模式-装饰器模式

news2024/9/23 11:27:59

装饰器模式也称为包装模式是指在不改变原有对象的基础上,将功能附加到对象上,提供比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式

装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态的扩展类的功能

装饰器模式通用UML类图

主要角色有:

  1. 抽象组件(Component):可以是一个接口或者一个抽象类,充当被修饰类的原始对象,规定了被装饰对象的行为
  2. 具体组件(ConcreteComponent):就是被装饰对象,实现或者集成抽象组件的一盒对象
  3. 抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,一般情况下是一个抽象类(如果装饰器只有一个的话可以直接省掉这个类)其内部必然有一个属性是指向Component抽象组件的
  4. 具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上每个ConcreteDecorator都扩展了Conponent一种功能(当然也可以不扩展,如果不扩展不就是写了一个没用的类吗)

装饰器模式的通用实现代码

public interface Component {
    void doSomething();
}
复制代码
public class ConcreteComponent implements Component{
    @Override
    public void doSomething() {
        System.out.println("原始类做一些事情");
    }
}
复制代码
public abstract class Decorator implements Component{
    public Component component;
    public Decorator(Component component){
        this.component = component;
    }
}
复制代码
public class DecoratorA extends Decorator{
    public DecoratorA(Component component) {
        super(component);
    }

    @Override
    public void doSomething() {
        super.component.doSomething();
        System.out.println("DecoratorA 再帮忙做点事情");
    }
}
复制代码
public class DecoratorB extends Decorator{
    public DecoratorB(Component component) {
        super(component);
    }

    @Override
    public void doSomething() {
        super.component.doSomething();
        System.out.println("DecoratorB 再帮忙做点事情");
    }
}
复制代码
public class Client {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        component.doSomething();
        System.out.println("-----------");
        Component decoratorA = new DecoratorA(component);
        decoratorA.doSomething();
        System.out.println("-----------");
        Component decoratorB = new DecoratorB(decoratorA);
        decoratorB.doSomething();
    }
}
复制代码

装饰器模式的大致原理

让装饰器实现被包装类相同的接口(使得装饰器与被扩展类类型一样),并在构造函数中传入该接口对象,然后就可以在接口需要实现的方法中在被包装类对象的 现有功能上添加新功能,而且由于装饰器与被包装类属于同一类型,且构造函数的参数为其实现接口类,因此装饰器模式具备嵌套扩展功能,这样我们就能使用装饰器模式一层一层的对最底层被包装类进行功能拓展了

装饰器内部有个指针指向被包装的类,应为被包装类和包装类实现的都是同一个接口或者抽象类,所以这个指针可能指向的不是最原始的类可以是被包装好几层的类,所以就实现了一层一层的包装

代码示例(不使用装饰器模式)

举一个很常见的例子,比如课程购买,一个课程最初始的价格是100,但是到了618公司说要搞个活动打九折,没过两天又来个活动这个活动是8折,此时普通用户就只能参加618打九折,公司普通会员的用户公司给8折,超级会员用户公司给折上九折再打八折。

public class JavaCourse {
    public Double getPrise() {
        return 100.0;
    }
}
复制代码

618活动

public class ActivityAJavaCourse extends JavaCourse{
    public Double getActivityPrice() {
        return 100.0 * 0.9;
    }
}
复制代码

普通会员活动

public class ActivityBJavaCourse extends JavaCourse{
    public Double getActivityPrice() {
        return 100.0 * 0.8;
    }
}
复制代码

超级会员活动

public class ActivityBAndAJavaCourse extends JavaCourse{
    public Double getActivityPrice() {
        return 100.0 * 0.9 * 0.8;
    }
}
复制代码

客户端调用:

public class Client {
    public static void main(String[] args) {
        Integer vipType = 1;
        boolean hasActivity = true;
        if(hasActivity){
            if(vipType == 0){
                //普通用户
                ActivityAJavaCourse javaCourse = new ActivityAJavaCourse();
                System.out.println("课程价格:"+javaCourse.getActivityPrice());
            }else if(vipType == 1){
                //普通会员
                ActivityBJavaCourse javaCourse = new ActivityBJavaCourse();
                System.out.println("课程价格:"+javaCourse.getActivityPrice());
            }else{
                //超级会员
                ActivityBAndAJavaCourse javaCourse = new ActivityBAndAJavaCourse();
                System.out.println("课程价格:"+javaCourse.getActivityPrice());
            }
        }else{
            JavaCourse javaCourse = new JavaCourse();
            System.out.println("课程价格:"+javaCourse.getPrise());
        }

    }
}
复制代码

这里面有两个很尴尬的点

  1. 每个活动对应一个类这个可以理解已有活动组合还要创建一个类,很麻烦,类很多
  2. 我们可以看一下客户端调用,在支付的时候使用金额还要判断一下当前是否在活动中,如果是活动则用getActivityPrice来获取支付金额如果不在活动中则使用getPrise获取支付金额,就写了很多这样的垃圾代码,当然有人可能质疑说不要实现getActivityPrice而是覆盖父类的getPrise方法,这样写的话前端是没那么多问题了,但是这个代码对于后端来说又不符合里式替换原则,很恶心

针对以上两个痛点我们可以使用装饰器模式稍微改进一下代码

代码示例(使用装饰器模式优化)

public interface ICourse {
    Double getPrice();
}
复制代码

被装饰类

public class JavaCourse implements ICourse{
    public Double getPrice() {
        return 100.0;
    }
}
复制代码

抽象装饰器

public abstract class ActivityJavaCourse implements ICourse{
    protected ICourse course;

    public ActivityJavaCourse(ICourse course){
        this.course = course;
    }
}
复制代码

具体618活动装饰类

public class ActivityJavaCourseA extends ActivityJavaCourse{
    public ActivityJavaCourseA(ICourse course) {
        super(course);
    }

    @Override
    public Double getPrice() {
        Double price = super.course.getPrice();
        return price * 0.9;
    }
}
复制代码

会员折扣装饰类

public class ActivityJavaCourseB extends ActivityJavaCourse{

    public ActivityJavaCourseB(ICourse course) {
        super(course);
    }

    @Override
    public Double getPrice() {
        Double price = super.course.getPrice();
        return price * 0.8;
    }
}
复制代码

客户端使用

public class Client {
    public static void main(String[] args) {
        Integer vipType = 3;
        boolean hasActivity = true;
        JavaCourse javaCourse = new JavaCourse();
        if(hasActivity){
            if(vipType == 0){
                //普通用户
                ActivityJavaCourseA javaCourseA = new ActivityJavaCourseA(javaCourse);
                System.out.println("课程价格:"+javaCourseA.getPrice());
            }else if(vipType == 1){
                //普通会员
                ActivityJavaCourseB javaCourseB = new ActivityJavaCourseB(javaCourse);
                System.out.println("课程价格:"+javaCourseB.getPrice());
            }else{
                //超级会员
                ActivityJavaCourseA javaCourseA = new ActivityJavaCourseA(javaCourse);
                ActivityJavaCourseB javaCourseB = new ActivityJavaCourseB(javaCourseA);
                System.out.println("课程价格:"+javaCourseB.getPrice());
            }
        }else{
            System.out.println("课程价格:"+javaCourse.getPrice());
        }

    }
}
复制代码

可以发现很好的解决了上述的两个痛点,有人可能会说不满足里式替换原则,大家需要注意的是这里不再是类的继承而是很多类共同实现一个接口而已

装饰器模式的适用场景

通过上文的描述我们可以大概总结出装饰器模式的适用场景

  1. 用于扩展一个类的功能或者给一个类添加附加职责
  2. 动态的给一个对象添加功能,这些功能可以再动态的撤销
  3. 需要为一批兄弟类进行改装或者加装功能

装饰器模式的优缺点

优点:

  1. 可以在不改变原有对象情况下动态的给一个对象扩展功能
  2. 遵循开闭原则
  3. 不同装饰器可以随意排列组合

缺点:

  1. 类增多增加程序复杂性
  2. 动态装饰时多层装饰时会更复杂

装饰器模式、门面模式、代理模式区别

讲完装饰器模式看过前面几篇文章的人可能凌乱了,有点搞不清楚门面模式、静态代理模式、装饰器模式了,下面总结一下三者的区别

  1. 装饰器模式和门面模式一样都是特殊的静态代理模式(仅此点一样)
  2. 装饰器模式强调的代码的变化、门面模式强调的代码的封装,代理模式则是代码的增强
  3. 一般装饰器模式源对象是需要通过构造方法传入的,门面模式则是在内部初始化子系统的对象主要是对子系统的封装
  4. 静态代理模式和装饰器模式的差别就没有那么大主要是理念上的不同,比如使用装饰器层层装饰,如果改用静态代理层层代理也挺奇怪

作者:Potato_土豆
链接:https://juejin.cn/post/7166531323320860680
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12444.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

培训学校管理系统之家校管理

近年来,伴随着信息技术的进步。除了不少学校开展了数字化校园的建设,一些培训机构也在使用数字化系统进行管理。同时,由于招生数量的不断增长,如何解决学生、教师、家长三者之间的联系沟通问题,促进教学管理任务的有效…

排序(详解)

排序排序的概念常见的排序算法直接插入排序希尔排序(缩小增量排序)选择排序堆排序冒泡排序快排(递归)霍尔版本挖坑法前后指针法快排(非递归)归并排序(递归)归并排序(非递…

区块链动态化监管方案

前言 监控运维模块是区块链BaaS的核心模块之一,我们针对联盟链、主机和系统等多个监控对象提供丰富的监控指标。通过BaaS提供的综合监控大屏,用户可直观洞悉区块链业务全局,实现7*24小时监控全覆盖。 但随着BaaS业务的扩展,对监…

jsp教师教学信息管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 JSP 教师教学信息管理系统 是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库文,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为sqlserver2008…

微服务拆分总结(一)

微服务要解决的问题: 1、可以快速迭代; 2、解决三高问题(高并发,高可用,高性能) 什么时候拆分微服务,拆分的时机是什么? 提交频繁代码冲突; 模块之间耦合严重&#…

RocketMQ 重试机制详解及最佳实践

作者:斜阳 引言 本文主要介绍在使用 RocketMQ 时为什么需要重试与兜底机制,生产者与消费者触发重试的条件和具体行为,如何在 RocketMQ 中合理使用重试机制,帮助构建弹性,高可用系统的最佳实践。 RocketMQ 的重试机制…

[附源码]java毕业设计静谧空间自习室预订系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

服务器部署Vue2脚手架的PIXI游戏项目-知识点注意

文章目录安装PIXI框架方式一(安装)方式二(引入)javaScript代码位置initPixi方法组件挂载完毕后调用注意文件导入使用import一个个导入并命名使用setTimeout()方法使用一般方法调用表达式使用安装PIXI框架 方式一(安装…

高通量筛选检测方法-分子篇

分子水平的筛选更多的是检测酶/受体功能的改变或探针/蛋白质结合的抑制,或是检测蛋白质-配体结合的结构、动力学和亲和度。 下面将介绍了荧光偏振、荧光共振能量转移、酶联免疫吸附、表面等离子共振和核磁共振技术几种方法。 ■ 荧光偏振 荧光偏振是一项在高通量筛…

2-STM32GPIO输入之按键

文章目录1-硬件设计1.1 按键消斗1.1.1 RS触发器1.1.2 电容滤波2 按键电路设计2.1 软件消斗2.2 硬件消斗2.3 检测原理2-软件设计2.1 软件消斗2.1.1原理2.1.2 编程要点2.1.3 步骤2.2 代码编写2.2.1 主程序2.2.2 按键初始化2.2.2 按键扫描本章讲述GPIO输入的应用,使用独…

第03章_用户与权限管理

第03章_用户与权限管理1 用户管理1.1 登录MySQL服务器1.2 创建用户1.3 修改用户1.4 删除用户1.5 设置当前用户密码1.6 修改其它用户密码1.7 MySQL8密码管理(了解)2. 权限管理2.1 权限列表2.2 授予权限的原则2.3 授予权限2.4 查看权限2.5 收回权限3. 权限表3.1 user表3.2 db表3.…

【springboot】18、内置 Tomcat 配置和切换

文章目录基本介绍Tomcat配置切换其他Web服务总结基本介绍 SpringBoot 支持的 webServer有: Tomcat, Jetty, or Undertow,我们使用spring-boot-starter-web进行web开发时,默认使用的就是Tomcat,下面来说明一下tomcat的配置以及切换其他的Web服…

小啊呜产品读书笔记001:《邱岳的产品手记-05》第9讲 产品案例分析:Hopper的“人工智能” 第10讲 产品被抄袭了怎么办?

小啊呜产品读书笔记001:《邱岳的产品手记-05》第9讲 产品案例分析:Hopper的“人工智能” & 第10讲 产品被抄袭了怎么办?一、今日阅读计划二、泛读&知识摘录1、09 讲 产品案例分析:Hopper的“人工智能”2、10 讲 产品被抄袭…

《机器学习实战》8.预测数值型数据:回归

目录 预测数值型数据:回归 1 利用线性回归找到最佳拟合直线 2 局部加权线性回归 3 示例:预测鲍鱼的年龄 4 缩减系数来“理解”数据 4.1 岭回归 4.2 lasso 4.3 前向逐步回归 5 权衡偏差与方差 6 示例:预测乐高玩具套装的价格 6.1 收…

数字化转型指南发布,官方明确这样做!

上周,工信部《中小企业数字化转型指南》(以下简称《指南》)一经发布,便获得了大量官方媒体的转发,成为了几乎所有制造人的关注所在。制造企业数字化转型的标准路径首次被标准化,并传递给了更多的中国制造企…

python可以考的资格认证有哪些?

前言 可以考虑用Python做一个博客,或者仿制一个微博,或者仿制一个视频网站,或者仿制一个购物网站。界面简单一些,但是基础功能好用就行。(文末送读者福利) 2.或者学习用Python在网上爬一些数据&#xff0…

就地执行Windows Server2022升级

项目初期背景:“微软Windows Server 2012/2012 R2将于2023年10月停止支持 微软今天发出提醒,Windows Server 2012 和 Windows Server 2012 R2 将于 2023 年 10 月终止支持,届时将不再发布补丁更新。由于外企公司比较注重信息安全,对所有服务器需要确保有补丁修复更新,以便保…

一些逻辑漏洞案例

逻辑漏洞的一些案例 某edu高校逻辑漏洞弱口令 已提交该校,已修复 注册登陆 寻找上传点,无果,后缀名不可控 找到另一个登陆点,尝试使用之前注册的账户登陆、爆破、均无果 在测试找回密码处,发送admin用户发现返回管理…

云计算实验4 面向行业背景的大数据分析与处理综合实验

一、 实验目的 掌握分布式数据库接口Spark SQL基本操作,以及训练综合能力,包括:数据预处理、向量处理、大数据算法、预测和可视化等综合工程能力 二、 实验环境 Linux的虚拟机环境和实验指导手册 三、 实验任务 完成Spark SQL编程实验、…

[附源码]java毕业设计基于篮球云网站

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…