Java设计模式-装饰者模式Decorator

news2025/1/19 2:54:56

在这里插入图片描述

介绍

装饰者模式的核心思想是通过创建一个装饰对象(即装饰者),动态扩展目标对象的功能,并且不会改变目标对象的结构,提供了一种比继承更灵活的替代方案。需要注意的是,装饰对象要与目标对象实现相同的接口,或继承相同的抽象类;

另外装饰对象需要持有目标对象的引用作为成员变量,而具体的赋能任务往往通过带参构造方法来完成。

结构

装饰者模式包含四种类,分别是抽象构件类、具体构件类、抽象装饰者类、具体装饰者类,它们各自负责完成特定任务,并且相互之间存在紧密联系。
在这里插入图片描述
在这里插入图片描述

使用

有了上述的基本概念,我们将装饰者模式的使用步骤概括为:

  1. 创建抽象构件类,定义目标对象的抽象类、将要扩展的功能定义成抽象方法;
  2. 创建具体构件类,定义目标对象的实现类,实现抽象构件中声明的抽象方法;
  3. 创建抽象装饰者类,维护一个指向抽象构件的引用,并传入构造函数以调用具体构件的实现方法,给具体构件增加功能;
  4. 创建具体装饰者类,可以调用抽象装饰者类中定义的方法,并定义若干个新的方法,扩展目标对象的功能。

使用案例

我们在淘宝上购物时,经常会遇到很多平台和商家的优惠活动:满减、聚划算站内的百亿补贴券、店铺折扣等等。那么在商品自身原价的基础上,叠加了多种优惠活动后,后台应该怎样计算最终的下单结算金额呢?下面就以这种优惠叠加结算的场景为例,简单分析装饰者模式如何使用。

UML类图

在这里插入图片描述

代码实现

作者:阿里巴巴大淘宝技术
链接:https://www.zhihu.com/question/32007641/answer/2750755082
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

// 定义抽象构件:抽象商品
public interface ItemComponent {
    // 商品价格
    public double checkoutPrice();
}

// 定义具体构件:具体商品
public class ConcreteItemCompoment implements ItemComponent {
    // 原价
    @Override
    public double checkoutPrice() {
        return 200.0;
    }
}

// 定义抽象装饰者:创建传参(抽象构件)构造方法,以便给具体构件增加功能
public abstract class ItemAbsatractDecorator implements ItemComponent {
    protected ItemComponent itemComponent;
    
    public ItemAbsatractDecorator(ItemComponent myItem) {
        this.itemComponent = myItem;
    }
    
    @Overrid
    public double checkoutPrice() {
        return this.itemComponent.checkoutPrice();
    }
}

// 定义具体装饰者A:增加店铺折扣八折
public class ShopDiscountDecorator extends ItemAbsatractDecorator {
    public ShopDiscountDecorator(ItemComponent myItem) {
        super(myItem);
    }
  
    @Override
    public double checkoutPrice() {
        return 0.8 * super.checkoutPrice();
    }
} 

// 定义具体装饰者B:增加满200减20功能,此处忽略判断逻辑
public class FullReductionDecorator extends ItemAbsatractDecorator {
    public FullReductionDecorator(ItemComponent myItem) {
        super(myItem);
    }
    
    @Override  
    public double checkoutPrice() {
        return super.checkoutPrice() - 20;  
    }
}

// 定义具体装饰者C:增加百亿补贴券50
public class BybtCouponDecorator extends ItemAbsatractDecorator { 
    public BybtCouponDecorator(ItemComponent myItem) {
        super(myItem);
    }
    
    @Override
    public double checkoutPrice() {
        return super.checkoutPrice() - 50;
    }
}
    

//客户端调用
public class userPayForItem() {
    public static void main(String[] args) {
        ItemCompoment item = new ConcreteItemCompoment();
        System.out.println("宝贝原价:" + item.checkoutPrice() + " 元"; 
        item = new ShopDiscountDecorator(item);
        System.out.println("使用店铺折扣后需支付:" + item.checkoutPrice() + " 元";
        item = new FullReductionDecorator(item);
        System.out.println("使用满200减20后需支付:" + item.checkoutPrice() + " 元";                
        item = new BybtCouponDecorator(item);
        System.out.println("使用百亿补贴券后需支付:" + item.checkoutPrice() + " 元";      
    }
}
  • 结果输出
    在这里插入图片描述

比较分析

  • VS 继承
    装饰者模式和继承关系都是要对目标类进行功能扩展,但装饰模式可以提供比继承更多的灵活性:继承是静态添加功能,在系统运行前就会确定下来;装饰者模式是动态添加、删除功能。

比如,一个对象需要具备 10 种功能,但客户端可能要求分阶段使用对象功能:在第一阶段只执行第 1-8 项功能,第二阶段执行第 3-10 项功能,这种场景下只需先定义好第 3-8 项功能方法。在程序运行的第一个阶段,使用具体装饰者 A 添加 1、2 功能;在第二个运行阶段,使用具体装饰者 B 添加 9、10 功能。而继承关系难以实现这种需求,它必须在编译期就定义好要使用的功能。

  • VS 代理模式

装饰者模式常常被拿来和代理模式比较,两者都要实现目标类的相同接口、声明一个目标对象,并且都可以在不修改目标类的前提下进行方法扩展,整体设计思路非常相似。那么两者的区别是什么呢?

首先,装饰者模式的重点在于增强目标对象功能,而代理模式的重点在于保护和隐藏目标对象。其中,装饰者模式需要客户端明确知道目标类,才能对其功能进行增强;代理模式要求客户端对目标类进行透明访问,借助代理类来完成相关控制功能(如日志记录、缓存设置等),隐藏目标类的具体信息。可见,代理类与目标类的关系往往在编译时就确定下来,而装饰者类在运行时动态构造而成

其次,两者获取目标类的方式不同。装饰者模式是将目标对象作为参数传给构造方法,而代理模式是通过在代理类中创建目标对象的一个实例。

最后,通过上述示例可发现,装饰者模式会使用一系列具体装饰者类来增强目标对象的功能,产生了一种连续、叠加的效应;而代理模式是在代理类中一次性为目标对象添加功能。

  • VS 适配器模式

两者都属于包装式行为,即当一个类不能满足需求时,创建辅助类进行包装以满足变化的需求。但是装饰者模式的装饰者类和被装饰类都要实现相同接口,或者装饰类是被装饰类的子类;而适配器模式中,适配器和被适配的类可以有不同接口,并且可能会有部分接口重合。

JDK源码分析

Java 的 IO 结构,FilterInputStream 就是一个装饰者。
如下图所示,InputStream 相当于抽象构件,FilterInputStream 类似于抽象装饰者,它的四个子类等同于具体装饰者。其中,FilterInputStream 中含有被装饰类 InputStream 的引用,其具体装饰者及各自功能为:PushbackInputStream 能弹出一个字节的缓冲区,可将输入流放到回退流中;DataInputStream 与 DataOutputStream搭配使用,用来装饰其它输入流,允许应用程序以一种与机器无关的方式从底层输入流中读取基本 Java 数据类型;BufferedInputStream 使用缓冲数组提供缓冲输入流功能,在每次调用 read() 方法时优先从缓冲区读取数据,比直接从物理数据源读取数据的速度更快;LineNumberInputStream 提供输入流过滤功能,可以跟踪输入流中的行号(以回车符、换行符标记换行)。

在这里插入图片描述
FilterInputStream 是所有装饰器类的抽象类,提供特殊的输入流控制。下面源码省略了 skip、available、mark、reset、markSupported 方法,这些方法也都委托给了 InputStream 类。其中, InputStream 提供装饰器类的接口,因而此类并没有对 InputStream 的功能做任何扩展,其扩展主要交给其子类来实现。

public class FilterInputStream extends InputStream {
    //维护一个 InputStream 对象
    protected volatile InputStream in;  
    
    //构造方法参数需要一个 inputStream
    protected FilterInputStream(InputStream in) {     
        this.in = in;
    }
 
    //委托给 InputStream
    public int read() throws IOException {
        return in.read();                            
    }
    
    //委托给 InputStream
    public void close() throws IOException {         
        in.close();
    }
    
    .......
   
}

优缺点及适用场景

  • 优点:
  1. 提供比继承更加灵活的扩展功能,通过叠加不同的具体装饰者的方法,动态地增强目标类的功能。
  2. 装饰者和被装饰者可以独立发展,不会相互耦合,比如说我们想再加一个炒河粉只需创建一个炒河粉类继承FastFood即可,而想要增加火腿肠配料就增加一个类去继承 Garnish 抽象装饰者。
  • 缺点:
    使用装饰模式,可以比使用继承关系创建更少的类,使设计比较易于进行。然而,多层装饰会产生比继承更多的对象,使查错更加困难,尤其是这些对象都很相似。而且,当目标类被多次动态装饰后,程序的复杂性也会大大提升,难以维护。

  • 适用场景:

  1. 继承关系不利于系统维护,甚至不能使用继承关系的场景。比如,当继承导致类爆炸时、目标类被 final 修饰时,都不宜通过创建目标类的子类来扩展功能。
  2. 要求不影响其他对象,为特定目标对象添加功能。
  3. 要求动态添加、撤销对象的功能。

转自:https://www.zhihu.com/question/32007641/answer/2750755082

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

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

相关文章

VisualBox解决CentOS中yum安装失败的问题

怎么说呢,花了一个下午一个上午的时间,总算把这条命令运行成功了: yum install wget -y 打怪兽途中遇到了几个问题,总结一下: 1.ping 啥ip都是unkown,还有一种情况,就是ping之后就一直卡在那…

C++计算机课程设计 学生成绩管理系统 研究报告

课程设计内容 2.1 学生成绩管理系统 2.1.1 内容 主菜单模块 该模块主要用来实现整个系统的流程。主界面提供用户选择并调用各个子模块。 输入模块 当初次使用系统时,学生信息需要从键盘逐个输入。学生信息由学生的学号、姓名、性别、高等数学、英语、计算机和平均…

Qt基于CTK Plugin Framework搭建插件框架--CTK服务工厂

一、前言 注册服务的时候能够用服务工厂来注册; 访问服务getServeice中的plugin参数是执行ctkPluginContext::getService(const ctkServiceReference&)的插件,从而工厂根据执行的不同插件名称返回不同的服务实现 服务工厂的作用 在服务中可以知道…

达摩院2023十大科技趋势发布,生成式AI将进入应用爆发期

1月11日,达摩院2023十大科技趋势发布,生成式AI、Chiplet模块化设计封装、全新云计算体系架构等技术入选。达摩院认为,全球科技日趋显现出交叉融合发展的新态势,尤其在信息与通信技术(ICT)领域酝酿的新裂变&…

【关于Linux中----进程间通信方式之管道】

文章目录一、什么是管道二、匿名管道三、命名管道一、什么是管道 进程间通信主要目的为以下几点 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源。 通知事件:一个进程需要向另一个或一组进程发送消息…

STL forward_list 模拟实现

forward_list 概述 forward_list 是 C 11 新增的容器,它的实现为单链表。 forward_list 是支持从容器中的任何位置快速插入和移除元素的容器,不支持快速随机访问。forward_list 和 list 的主要区别在于,前者的迭代器属于单向的 Forward Ite…

二分法讲解

目录 一、前言 二、二分法理论 1、引导:猜数游戏 2、理论背景:非线性方程的求根问题 1)非线性方程的近似解 2)搜索法和二分法 3、用二分的两个条件 4、二分法复杂度 三、整数二分 1、在单调递增序列中找 x 或者 x 的后继…

使用python-pptx创建PPT演示文档功能实践

python对PPT演示文档读写,是通过第三方库python-pptx实现的,python-pptx是用于创建和更新 PowerPoint(.pptx)文件的 Python 库。 关于PPT演示文档与幻灯片模板的内容不是本文的重点,在此略过。 1. PPT基本结构在pyth…

Sophus降维、升维与欧拉角、旋转向量的爱恨情仇

0. 简介 在面对二维与三维之间的转换时,我们常常会困惑该如何去转换,在G2O中存在有理想的坐标转换工具,但是在Sophus中却缺乏这样的手段。之前在Sophus处简要的介绍了一下SE(2)与SE(3)的转换,最近发现之前的文章这部分需要拿出来…

Leetcode:654. 最大二叉树(C++)

目录 问题描述: 实现代码与解析: 直接模拟(递归): 原理思路: 索引版本: 问题描述: 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&a…

Thymeleaf从入门到清晰使用

文章目录什么是thymeleaf?第一个Thymeleaf程序Thymeleaf详解配置常用标签最后什么是thymeleaf? 模板引擎: 前端交给我们的页面,是html页面,如果是以前开发,我们需要把他们转成jsp页面,jsp的好处…

ABB AC500 系列 PLC 与上位机iFix 的通讯配置

ABB PLC IP 及 MODBUS TCP/IP 协议设置 通过 IP config tool 配置设备 IP 在 软件中,有 3 种方式可以进入 IP config tool 的配置界面  双击 IP_settings,点击 IP config tool,即可进入 IP config tool 界面 点击菜单栏的 Tool→IP confi…

k8s 微服务spring boot JVM 监控

目录 1.前言 1.1 JVM监控方案 1.2 接入操作步骤 2. 服务开启actuator prometheus监控 2.1 示例参考添加依赖 2.2 配置prometheus监控 3 配置 prometheus 监控 4 配置jvm grafana dashboard 1.前言 服务 部署环境 监控 spring-demo k8s v1.22 Prometheus Operator …

进程相关概念

1、什么是程序,什么是进程,有什么区别 程序是静态的概念。例如:gcc xxx.c -o pro,磁盘中生成pro文件,叫做程序(还未运行起来) 进程是程序的一次运行活动,也就是程序跑起来了&#xf…

【Linux】信号机制(非实时信号)

目录 前言 一.信号的概念以及产生 1.什么是信号 2.信号分为两类 3.查看信号的命令 4.信号如何产生 1).通过软件产生 2).通过硬件产生 3).通过键盘组合键产生 二.信号的发送以及保存 1.信号如何发送 2.信号如何保存 1).概念 2).底层实现结构&&内核中的实现…

WampServer服务器设置控件

WampServer服务器设置控件 WampServer是一个web开发环境,是一个用于Windows操作系统的类似服务器的软件,由Romain Bourdon构建。该工具允许您开始构建web应用程序,并在Windows上使用Apache服务器2、PHP编程语言和MySQL数据库在本地网络上启动…

java 实现 springboot项目 使用socket推送消息,前端实时进行接收后端推送的消息

1 后端 1.1 添加依赖 在我们的springboot项目里面&#xff0c;添加依赖&#xff1b; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.0</version>…

企业微信在职继承功能如何理解?怎么使用?

企业微信的在职继承功能就是企业管理员将在职员工的客户分配给其他在职员工跟进的一种方式&#xff0c;很方便&#xff0c;可以在工作出现变更时使用。 前言 企业微信之前的离职继承功能受到很多企业的青睐&#xff0c;企业能够将离职员工的客户分配给其他员工接手&#xff0c…

云天励飞在科创板获准注册:计划募资30亿元,陈宁为实际控制人

2023年1月10日&#xff0c;证监会发布公告&#xff0c;同意深圳云天励飞技术股份有限公司&#xff08;下称“云天励飞”&#xff09;首次公开发行股票注册。据贝多财经了解&#xff0c;云天励飞于2020年12月8日在科创板递交上市申请&#xff0c;2021年8月6日过会。 本次冲刺上市…

蓝队攻击的四个阶段(一)

蓝队的攻击是一项系统的工作&#xff0c;整个攻击过程是有章可循、科学合理的涵盖了从前期准备、攻击实施到靶标控制的各个步骤和环节。按照任务进度划分&#xff0c;一般可以将蓝队的工作分为4个阶段:准备工作、目标网情搜集、外网络向突破和内网横向拓展。第一阶段准备工作&a…