【设计模式】使用装饰器模式对价格计算功能做灵活的拓展

文章目录

  • 1. 概述
  • 2.实现方式
    • 2.1.原始商品类及计算接口
    • 2.2.加入装饰器
    • 2.3.装饰器的组合使用
  • 3.总结

1. 概述

装饰器模式(Decorator Pattern)是一种结构型的设计模式,使用组合的方式来替代了继承,它的核心作用是在不修改对象本身的基础上动态地给一个对象添加新的职责或功能,装饰器类一般与被装饰的类有同一个父类(或父接口),其后缀名一般Decorator或者Wrapper
讲到这里,如果熟悉设计模式的同学肯定能发现,这个定义与上一篇设计模式的文章《代理模式的实现方式与使用场景》中提到了代理模式是高度类似的,事实上也确实如此,都在一定程度上对原始对象的行为进行某种形式的包裹或扩展。

那么,装饰模式和代理模式有什么区别呢?
它们在功能实现上有很多地方都可以互换,两者只是在应用场景上划分了不同的区域:

  • 代理模式:适合于做与业务流程无关的增强,例如对象的访问控制、接口鉴权、RPC远程访问等
  • 装饰模式:适合于对业务功能动态添加额外的职责,这些职责和业务功能流程是紧密关联的。

也就是说,判断选择两种模式的使用,只需要看这次的拓展与业务流程是否有关就可以了,接下来看一下装饰模式的实现方式。

2.实现方式

在这里插入图片描述
先用类图来进行说明,在原来的功能接口(或功能抽象类)和功能实现类的基础上,定义了一个装饰器的抽象类,装饰器和功能类实现同一个接口,并且通过组合的方式依赖顶层的接口。如果对类图不熟悉的同学,可以参考《类图(Class Diagram)》。

这么做了之后,装饰器相对于原有的功能接口既是is-a的关系,也是has-a的关系,不管装饰器有多少个、包装了多少层,它始终都是原有功能接口的子类,并且在每一层中都会持有对上一层装饰器(或原有功能对象)的引用。


下面通过一个简单Demo场景来体验一下装饰器模式,如何在不改变原有的接口和实现类的情况下,通过装饰器动态的添加新的功能。

假如现在有一个商城服务,里面有一个计算购物车中商品的价格的功能,有这么几个需求:

  • 可以通过用户的优惠券做额度抵扣
  • 如果用户是会员,可以打九折
  • 如果不满足上面两种情景,则需要原价支付

按照需求,我们可以首先创建一个计算价格的接口,然后新增一个装饰器的抽象用于修改最终价格,最后通过两个不同的装饰器实例(优惠券、打折)来实现价格的重新计算。

2.1.原始商品类及计算接口

  • 商品类
    	@Datapublic class Product {/*** 商家价格(单位:分)*/private Long price;/*** 商品名称*/private String name;}
    
  • 价格计算接口及其实现
    public interface PriceCalculate {Long calculatePrice(List<Product> products);
    }public class PriceCalculateImpl implements PriceCalculate {@Overridepublic Long calculatePrice(List<Product> products) {return products.stream().mapToLong(Product::getPrice).sum();}
    }
    

2.2.加入装饰器

  • 抽象的价格计算装饰器
    通过组合的方式依赖价格计算的接口,再通过模板方法将价格的折扣与优惠券抵扣交给各自的实现类去实现。
    public abstract class PriceCalculateDecorator implements PriceCalculate {protected PriceCalculate priceCalculate;public PriceCalculateDecorator(PriceCalculate priceCalculate) {this.priceCalculate = priceCalculate;}@Overridepublic Long calculatePrice(List<Product> products) {return this.discountPrice(products);}public abstract Long discountPrice(List<Product> products);}
    
  • 会员折扣装饰器
    这里做了简单的处理,直接写死折扣,实际的场景中应该从会员折扣配置中获取到折扣的值,并且应该通过Bigdecimal对金额做计算。
/*** 会员折扣装饰器*/
public class PriceVipDiscountDecorator extends PriceCalculateDecorator {public PriceVipDiscountDecorator(PriceCalculate priceCalculate) {super(priceCalculate);}@Overridepublic Long discountPrice(List<Product> products) {Long originPrice = priceCalculate.calculatePrice(products);// 会员九折(此处为简化,实际应该从会员服务获取会员折扣)return originPrice * 9 / 10;}
}
  • 优惠券抵扣装饰器
    和上面的会员抵扣一样,做简化处理
    /*** 优惠券抵扣装饰器*/
    public class PriceCouponDeductionDecorator extends PriceCalculateDecorator {public PriceCouponDeductionDecorator(PriceCalculate priceCalculate) {super(priceCalculate);}@Overridepublic Long discountPrice(List<Product> products) {Long originPrice = priceCalculate.calculatePrice(products);// 优惠券抵扣(此处为简化,实际应该从优惠券服务获取优惠券抵扣金额)return Math.max(originPrice - 5000, 0);}
    }
    

最后,写一个测试来验证一下,在这个测试中,分别用VIP折扣和优惠券来做抵扣。

public void testWrapper() {List<Product> products = getProduct();PriceCalculate priceCalculate = new PriceCalculateImpl();Long price = priceCalculate.calculatePrice(products);System.out.println("原价:" + price + "分");// 会员折扣PriceCalculate vipDiscountDecorator = new PriceVipDiscountDecorator(priceCalculate);Long vipPrice = vipDiscountDecorator.calculatePrice(products);System.out.println("会员折扣后,价格为:" + vipPrice + "分");// 优惠券抵扣PriceCalculate couponDiscountDecorator = new PriceCouponDeductionDecorator(priceCalculate);Long couponPrice = couponDiscountDecorator.calculatePrice(products);System.out.println("优惠券抵扣后,价格为:" + couponPrice + "分");}private List<Product> getProduct() {List<Product> products = new ArrayList<>();// 创建商品Product product = new Product();product.setName("商品1");product.setPrice(100L * 100);products.add(product);Product product2 = new Product();product2.setName("商品2");product2.setPrice(200L * 100);products.add(product2);return products;}

在这里插入图片描述

2.3.装饰器的组合使用

除了上面的分别计算以外,两个装饰器实例还可以组合起来使用,例如现在有个用户既是VIP会有,又有50块的优惠券,按照先打折再优惠券抵扣的规则,可以将VIP装饰器实例通过构造方法直接传入到优惠券装饰器中,修改如下:

        List<Product> products = getProduct();PriceCalculate priceCalculate = new PriceCalculateImpl();Long price = priceCalculate.calculatePrice(products);System.out.println("原价:" + price + "分");// 先打折,再优惠券抵扣PriceCalculate vipDiscountDecorator = new PriceVipDiscountDecorator(priceCalculate);PriceCouponDeductionDecorator priceCouponDeductionDecorator = new PriceCouponDeductionDecorator(vipDiscountDecorator);Long discountPrice = priceCouponDeductionDecorator.calculatePrice(products);System.out.println("折扣价:" + discountPrice + "分");

在这里插入图片描述
如果需求是先抵扣优惠券再打折的话,只需要调整一下装饰器的包装顺序就可以了,如果还有其他的活动折扣,满减之类的对价格计算的调整,再跟进具体的需求新增装饰器并包装起来即可。

3.总结

装饰器的作用是在不改变目标类的情况下,动态的添加功能或职责,上述的功能使用继承也可以实现,但是使用继承之后,一旦修改了父类的方法所有子类都会受到影响,而使用装饰器的方式,目标对象与装饰器对象并不会互相影响。

另外,需要注意到的是装饰模式和代理模式非常相似,从功能上来讲,一个可以实现的功能使用另一个也可以实现,我们在使用的过程中只需要注意一点,即:代理模式用于实现非业务的功能增强,装饰器模式往往用于对业务功能的增强

最后,再谈一下装饰器的缺点,包装的层数过多的时候会给我们的问题排查带来一定的困难,需要一层一层的往里面剥才有可能找到是哪个装饰器出现的问题,所以我们在使用的时候尽可能的减少装饰器包装的层数,两到三层即可。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

值得收藏的的适用于 Windows 11 的免费数据恢复软件榜单

终于要说到Windows 11了&#xff0c;有太多令人惊叹的功能&#xff0c;让人跃跃欲试。但是&#xff0c;在升级到 Windows 11 或使用 Windows 11 时&#xff0c;人们可能会因计算机问题而导致文件被删除或丢失。这就是为什么需要 Windows 11 的免费文件恢复的原因。这是适用于 W…

Intelij Terminal中文乱码解决

第一&#xff1a; &#xff08;重启Intelij生效&#xff09; -Dfile.encodingUTF-8 第二&#xff1a; &#xff08;重启Intelij生效&#xff09; 如果还不行&#xff0c;第三&#xff1a; 测试结果很ok&#xff1a;

备战蓝桥杯---图论之最小生成树

首先&#xff0c;什么是最小生成树&#xff1f; 他就是无向图G中的所有生成树中树枝权值总和最小的。 如何求&#xff1f; 我们不妨采用以下的贪心策略&#xff1a; Prim算法&#xff08;复杂度&#xff1a;&#xff08;nm)logm)&#xff1a; 我们对于把上述的点看成两个集…

《Go 简易速速上手小册》第10章:微服务与云原生应用(2024 最新版)

文章目录 10.1 构建微服务架构 - 探索 Go 语言的微观世界10.1.1 基础知识讲解10.1.2 重点案例&#xff1a;订单处理系统订单服务测试服务 10.1.3 拓展案例 1&#xff1a;用户认证服务安装所需的包实现用户模型和存储实现 JWT 生成和验证实现认证服务测试服务 10.1.4 拓展案例 2…

[嵌入式系统-28]:开源的虚拟机监视器和仿真器:QEMU(Quick EMUlator)与VirtualBox、VMware Workstation的比较

目录 一、QEMU概述 1.1 QEMU架构 1.2 QEMU概述 1.3 什么时候需要QEMU 1.4 QEMU两种操作模式 1.5 QEMU模拟多种CPU架构 二、QEMU与其他虚拟机的比较 2.1 常见的虚拟化技术 2.1 Linux KVM 2.2 Windows VirtualBox 2.3 Windows VMware workstation 三、VirtualBox、VM…

C++模板详解 —— 函数模板与类模板

C模板详解 泛型编程函数模板函数模板的概念函数模板的原理 函数模板的实例化函数模板的匹配原则类模板类模板的定义格式类模板的实例化 泛型编程 如果让你编写一个函数&#xff0c;用于两个数的交换。在C语言中&#xff0c;我们会用如下方法&#xff1a; void Swapi(int* p1,…

如何简单上手清华AutoGPT并搭建到本地环境

一、准备工作 安装Docker&#xff1a;确保你的本地机器上已经安装了Docker。如果还没有安装&#xff0c;请访问Docker官方网站并按照指引进行安装。--点击进入Docker官网 获取清华AutoGPT的Docker镜像&#xff1a;清华AutoGPT团队可能已经提供了一个Docker镜像&#xff0c;方便…

用于图像处理的Python顶级库 !!

文章目录 前言 1、OpenCV 2、Scikit-Image 3、Scipy 4、Python Image Library&#xff08;Pillow / PIL&#xff09; 5、Matplotlib 6、SimpleITK 7、Numpy 8、Mahotas 前言 正如IDC所指出的&#xff0c;数字信息将飙升至175ZB&#xff0c;而这些信息中的巨大一部分是图片。数…

2.9日学习打卡----初学RabbitMQ(四)

2.9日学习打卡 目录&#xff1a; 2.9日学习打卡一.RabbitMQ 死信队列创建死信队列测试死信队列 二. RabbitMQ 延迟队列延迟队列_死信队列实现RabbitMQ延迟队列_插件实现 三. RabbitMQ集群搭建RabbitMQ集群镜像队列负载均衡 一.RabbitMQ 死信队列 在MQ中&#xff0c;当消息成为死…

php基础学习之可变函数(web渗透测试关键字绕过rce和回调函数)

可变函数 看可变函数的知识点之前&#xff0c;蒟蒻博主建议你先去看看php的可变变量&#xff0c;会更加方便理解&#xff0c;在本篇博客中的第五块知识点->php基础学习之变量-CSDN博客 描述 当一个变量所保存的值刚好是一个函数的名字&#xff08;由函数命名规则可知该值必…

【复现】某公司指挥调度管理平台 RCE漏洞_51

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 该平台提供强大的指挥调度功能&#xff0c;可以实时监控和管理通信网络设备、维护人员和工作任务等。用户可以通过该平台发送指令…

AD9361多片同步设计方法

本文基于ZC706FMCOMMS5的平台&#xff0c;介绍了多片AD9361同步的方法。并将该设计移植到自行设计的ZYNQ70354片AD9361(实现8路同步收发)的电路板上。 工程主要特点包括&#xff1a; 本设计采用纯逻辑的方式 1.4片AD9361组合&#xff0c;组成8通道发和8通道收 2.同步相位误差小…

[HTML]Web前端开发技术26(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

PLC-Recorder的延伸分析功能说明

目录 一、缘起 二、如何从PLC-Recorder获取数据 1、在线获取 2、全自主打开数据文件 3、延伸分析 三、设置方法 四、效果展示 一、缘起 在各个行业&#xff0c;在不同的场景中&#xff0c;朋友们拿到数据后&#xff0c;想做的事情五花八门&#xff0c;有做宏观分析的、…

wps使用方法(包括:插入倒三角符号,字母上面加横线,将word中的所有英文设置为time new roman)

倒三角符号 字母上面加横线 将word中的所有英文设置为time new roman ctrla选中全文

Java基于微信小程序的医院挂号小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

作业帮 x TiDB丨多元化海量数据业务的支撑

导读 作业帮是一家成立于 2015 年的在线教育品牌&#xff0c;致力于用科技手段助力教育普惠。经过近十年的积累&#xff0c;作业帮运用人工智能、大数据等技术&#xff0c;为学生、老师、家长提供学习、教育解决方案&#xff0c;智能硬件产品等。随着公司产品和业务场景越来越…

CSS概述 | CSS的引入方式 | 选择器

文章目录 1.CSS概述2.CSS的引入方式2.1.内部样式表2.2.行内样式表2.3.外部样式表 3.选择器 1.CSS概述 CSS&#xff0c;全称Cascading Style Sheets&#xff08;层叠样式表&#xff09;&#xff0c;是一种用来设置HTML&#xff08;或XML等&#xff09;文档样式的语言。CSS的主要…

AI专题:5G-A扬帆风正劲,踏AI增长新浪潮

今天分享的是AI系列深度研究报告&#xff1a;《AI专题&#xff1a;5G-A扬帆风正劲&#xff0c;踏AI增长新浪潮》。 &#xff08;报告出品方&#xff1a;开源证券&#xff09; 报告共计&#xff1a;22页 足立连接&#xff0c;拓展算力&#xff0c;双曲线稳步发力 中兴通讯拥…

Rocky Linux 下载安装

一、VMware Workstation下载安装 1、安装教程 VMware Workstation下载安装&#xff08;含密钥&#xff09; 二、VMware Workstation 创建虚拟机 1、创建教程 VMware Workstation 创建虚拟机 三、Rocky Linux 下载 1、下载官网 RockyLinux.org 2、选择X86架构_64位系统_DVD镜…