设计模式(六):装饰器模式_编程队伍的博客-程序员秘密

技术标签: 设计模式  

俄罗斯套娃大家都玩过吧,就像是这样

这玩意玩起来很上头,打开一个总期待会有下一个,充满了趣味性

程序员在写代码时,也会遇到像套娃这样令人上头的代码

打开一个类,里面还有一个类,再打开一个,里面还有一个...

这种套娃似的代码其实是一种很常见的设计模式,它叫装饰器模式

今天我们就来扒一扒装饰器模式到底是个什么东西

实际案例

假如我们要写一个支付的功能,支付的方式有支付宝和微信

我们用代码来实现这个功能

首先我们要定义一个接口类Payment,这个接口类用来规定支付功能应该有哪些行为,也就是应该有哪些方法

比如可以有支付查询两个方法,对应的方法名分别是pay()query()

然后分别定义一个支付宝类和微信类实现Payment接口类,并重写支付查询的方法

这样,支付功能就写好了,在需要支付的时候直接调用就可以了

比如,需要使用支付宝支付时,可以这样调用

Payment payment = new AliPayment();
payment.pay("赫连小伍测试支付宝支付");
payment.query("赫连小伍测试支付宝查询");

退款装饰器

在系统运行一段时间后,发现用户会有支付错误的情况,需要退款

这时候我们就需要新增退款功能

从实际业务考虑,退款属于整个支付功能中的一个行为。所以,应该在Payment接口类中增加退款的方法,支付宝类和微信类再分别实现这个方法

按照这个逻辑去修改代码,确实可以满足退款的要求,但是需要考虑一个问题

在我们的案例中只有支付宝和微信两个支付方式,真实场景中是有很多支付方式的。比如说华为支付、小米支付、云闪付、中行支付、交行支付、农行支付、工行支付等等,各种各样的支付方式加起来也有上百个

Payment接口类上增加退款方法,意味着这上百个支付方式的类都要去实现这个方法,否则代码就编译报错

工作量太大了,耗费时间太多,项目经理不可能给研发争取这么多的时间的

我们只能想别的办法,比如可以这样去实现:再定义一个类作为支付功能的扩展类,用来扩展退款功能,命名为RefundDecorator,提供一个refund()退款方法

它既然作为支付功能的扩展类,还应该具备支付应有的功能吧,也就是说它应该有支付查询的方法,所以它应该实现Payment接口类

实现这个接口类就意味着要重写pay()query()两个方法

重写太麻烦了,而且每个支付方式类都已经重写过这两个方法了,可以直接拿过来用。

我们把Payment类作为RefundDecorator的一个属性注入就来,这样就可以使用Payment类的pay()query()两个方法了

在使用Payment类这个属性之前,还要给它提供一个实例化的机会

基于以上要求,我们划一下重点,RefundDecorator类的修改逻辑如下

  1. 实现 Payment接口类,并重写 pay()query()两个方法
  2. Payment对象作为一个属性,并在构造器中对其进行实例化

用代码实现就是这个样子

修改过后,用户再使用支付宝支付功能时就可以这样调用

RefundDecorator refundDecorator = new RefundDecorator(new AliPayment());
refundDecorator.pay("赫连小伍测试支付宝支付");
refundDecorator.query("赫连小伍测试支付宝查询");
refundDecorator.refund("赫连小伍测试支付宝退款"); // 退款

其实,RefundDecorator类就是一个装饰器,这种编码方式就是装饰器模式

文章开头也说了,装饰器模式是套娃的,这里也看不出来啊

先别着急,我们再把这个案例延申一下

对账适配器

我们系统又运行了一段时间,用户既可以支付又可以退款了,用户很满意

可是,商家不满意了。因为系统每天统计的商家收入和商家的实际收入有差异,比如,昨天商家实际收入1万元,可是系统统计的只有9千元

商家对我们的系统极其不信任,要求我们必须尽快修复问题

这时候我们就需要引入对账功能,用来保证商家的实际收入和系统统计的收入数据一致

按照我们刚才添加退款功能时的逻辑来分析

不能在Payment类中增加对账方法,因为所有子类都需要重写该方法,工作量太大

只能新增一个账单类BillDecorator,里面提供一个对账方法check()

账单类BillDecorator作为支付功能的扩展类,它应该具备支付的所有能力,包括支付查询退款

退款扩展类RefundDecorator正好有这三个方法,所以让账单类直接继承退款扩展类就可以了

但是别忘了,在退款类中需要给Payment提供实例化的机会,目的在于调用Payment类的pay()query()方法

所以在账单类中也要给Payment提供实例化的机会,它才也能调用这两个方法

还有一个refund()方法不能调用,所以还要给退款方法所在的类RefundDecorator提供实例化的机会

总结一下就是账单类里面要给PaymentRefundDecorator两个类提供实例化的机会

因为RefundDecorator类中已经给Payment提供了实例化方法,所以我们只需要在账单类里面给RefundDecorator类提供实例化方法就可以了

ps:上面这段逻辑稍微有点绕,但是并不复杂,都是面向对象编程中继承的一些基础知识点,细读几遍很容易理解的

所以,账单类的代码应该这样写

这样我们就完成了对账功能,其实这个账单类也是一个装饰器

在需要对账的时候可以这样调用

BillDecorator billDecorator = new BillDecorator(
         new RefundDecorator(
           new AliPayment()));
billDecorator.pay("赫连小伍测试支付宝支付");
billDecorator.query("赫连小伍测试支付宝查询");
billDecorator.refund("赫连小伍测试支付宝退款");
billDecorator.check("赫连小伍测试支付宝对账");

上面的代码在new对象的时候是不是像极了套娃

如果后期需求一直不断增加,我们的代码可以一直套娃下去

套娃一时爽,一直套娃一直爽

总结

装饰器模式属于设计模式三大类型中的结构型设计模式,它的主要作用是通过将父类对象作为子类对象的属性从而给父类对象绑定新的行为

在你希望增加原有对象的行为,而又不想要修改原有对象时,可以考虑使用装饰器模式

装饰器模式的实现步骤总共有两步:

  1. 提供一个新的类定义需要扩展的方法,并使这个类继承或实现原有对象,让其拥有原有对象的所有行为
  2. 将原有对象作为新定义的类的属性,并为其提供实例化的方法

优点

  • 无需修改原有对象即可扩展它的行为
  • 可以通过多个装饰器为对象绑定不同的行为,使对象的使用更灵活

缺点

  • 装饰器的层级递进关系比较明显,想要实现不受顺序影响的装饰器比较困难
  • 装饰器层级比较深时,调用时 套娃比较多,代码看起来不美观

与适配器模式比较

装饰器在可以不改变对象接口的前提下实现对象功能的扩展,适配器模式有时还需要修改接口

装饰器可以递归实现对象行为的多层绑定,适配器不能递归

装饰器主要用来扩展对象的功能,适配器主要为对象原有的功能提供更多的使用场景

还是那句话,学会设计模式不是目的,理解设计模式隐含的设计思想才能无往不利

技术需要沉淀,我们下期再见

-- 以上内容来自公众号 赫连小伍 ,转载请注明出处

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/as576228266/article/details/120709451

智能推荐

maven 打包时缺少文件_maven 打包jar 提示找不到依赖的类_PayMillet的博客-程序员秘密

以下描述为对问题的 抽象描述项目A 打包生成JarA,项目A依赖了Netty包和一个自己本地的JarB包打包之后JarA的 文件结构 如下:(1)JarA包的根路径下存在 JarB 和NettyJar(2)在JarA的Manifest文件中指定了 mainClass和classpath:jarB.jar nett-all.jar这样配置之后 运行jarA ,按理来说 jarA都能够根据clas...

【附答案】Java 大数据方向面试题,你会几个?_weixin_30454481的博客-程序员秘密

未来10年将是大数据,人工智能爆发的时代,到时将会有大量的企业需要借助大数据,而Java最大的优势就是它在大数据领域的地位,目前很多的大数据架构都是通过Java来完成的。你的Java内功如何呢?一起来做做Java大数据方向的面试题~~1、Collection 和 Collections的区别。Collections是个java.util下的类,它包含有各种有关集合操作的静态方法...

CSND编辑器的使用 --- Markdown编辑器_csnd csdneditor_那酷小样的博客-程序员秘密

本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:Markdown和扩展Markdown简洁的语法代码块高亮图片链接和图片上传LaTex数学公式UML序列图和流程图离线写博客导入导出Markdown文件丰富的快捷键这里写代码片快捷键加粗 Ctrl + B 斜体 Ctrl + I 引用 Ctrl + Q...

LeetCode 295. 数据流的中位数(大小堆)_大小堆 中位数_Michael阿明的博客-程序员秘密

文章目录1. 题目2. 大小堆解题1. 题目中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。例如,[2,3,4] 的中位数是 3[2,3] 的中位数是 (2 + 3) / 2 = 2.5设计一个支持以下两种操作的数据结构:void addNum(int num) - 从数据流中添加一个整数到数据结构中。double findMedian() - 返回...

iOS生成同时支持armv7,armv7s,i386 x86_64的静态库.a文件_lisiben的博客-程序员秘密

许多第三方提供的.a文件(一般是那些SDK),嵌入到我们的xcode项目后,生成不会报错。 一部分粗心的SDK提供方,或者我们自己做的.a文件,就会有报错,常见的就是不是armv7结构,或者不是armv7s结构,如果在模拟器中运行,会报不是i386结构。 原因是:armv7是许多第三方提供的.a文件(一般是那些SDK),嵌入到我们的xcode项目后,生成不会报错。一部分粗心的SDK提供...

Python图像处理之小波变换_python opencv 图像小波变换_白马负金羁的博客-程序员秘密

小波变换或小波分析在很多领域中都有重要应用,例如石油勘探、数据压缩,以及接下来所要探讨的图像处理等。作为后续系列文章的铺垫,本文将主要介绍在Python中进行小波分析所需要用到的一个重要的软件包PyWavelets。关于小波的一些原理性的知识,请参考我已出版的《图像处理中的数学修炼》一书

随便推点

Linux主要shell命令详解_linux命令行注释符号_miaozk2006的博客-程序员秘密

shell是用户和Linux操作系统之间的接口。Linux中有多种shell,其中缺省使用的是Bash。本章讲述了shell的工作原理,shell的种类,shell的一般操作及Bash的特性。  什么是shell Linux系统的shell作为操作系统的外壳,为用户提供使用操作系统的接口。它是命令语言、命令解释程序及程序设计语言的统称。 shell是用户和Linux内核之间的接口程序,如...

“黑天鹅”飞过,汽车与科技如何深度融合?_ITValue的博客-程序员秘密

关注ITValue,看企业级最新鲜、最具价值报道!谁也不曾想到2020年的开局会是这样,突如其来的 “黑天鹅”,不仅打乱了人们的生活节奏,也给全球经济...

transformers库的使用【二】tokenizer的使用,模型的保存自定义_autotokenizer.from_pretrained_元宇宙iwemeta的博客-程序员秘密

使用标记器(tokenizer)在之前提到过,标记器(tokenizer)是用来对文本进行预处理的一个工具。首先,标记器会把输入的文档进行分割,将一个句子分成单个的word(或者词语的一部分,或者是标点符号)这些进行分割以后的到的单个的word被称为tokens。第二步,标记器会把这些得到的单个的词tokens转换成为数字,经过转换成数字之后,我们就可以把它们送入到模型当中。为了实现这种能把tokens转换成数字的功能,标记器拥有一个词表,这个词汇表是在我们进行实例化并指明模型的时候下载的

okhttp统一处理返回请求 okhttp统一处理返回数据 okhttp4.2统一处理返回请求_okhttp 统一错误处理_代码s贝多芬的音符的博客-程序员秘密

使用//return response 因为response.body().byte() 或response.body()string()之后,response中的流会被关闭,程序会报错,我们需要创建出一 个新的response给应用层处理。 所以 return response.newBuilder() .body(...

2016年你要学哪些语言和框架_hubeitmkk的博客-程序员秘密

2016年,软件开发界涌现了不少新的编程语言、重要的框架以及工具。杭州达内培训专家给出了2016年你值得学习的语言/框架的建议。   软件开发的趋势    最近有个日益明显的趋势是web应用的商业逻辑逐步从后端转移到了前端,然后后端变得只需要处理简单的数据API,因此前端开发的框架选择也就更加重要了。    另一个重大的变化是2015年发布的 Edge 浏览器。

Could not transfer artifact (https://repo.maven.apache.org/maven2): Received fatal alert: protocol_v_清欢ysy的博客-程序员秘密

异常信息Failed to read artifact descriptor for org.slf4j:slf4j-api:jar:1.7.2org.eclipse.aether.resolution.ArtifactDescriptorException: Failed to read artifact descriptor for org.slf4j:slf4j-api:jar:1.7....

推荐文章

热门文章

相关标签