3年工作必備 裝飾器模式
好啦,进入我们的主题,今天我给大家分享设计模式中的装饰器模式
。用贴切的生活故事,以及真实项目场景来讲设计模式,最后用一句话来总结这个设计模式。
故事
古话说的好:人靠衣裳马靠鞍。下面先带大家来熟悉这句话的背景:
人靠衣装马靠鞍,狗配铃铛跑的欢出自沈自晋《望湖亭记》第十出:“虽然如此,佛靠金装,人靠衣装,打扮也是很要紧的。”《醒世恒言》卷一‧两县令竞义婚孤女:”常言道:’佛是金装,人是衣装,世人眼孔浅的多,只有皮相,没有骨相。’”俗语我们会说成人靠衣装马靠鞍。
这个经典故事,让我想起了一个设计模式:装饰器模式。
什么是装饰器模式呢?请听老田慢慢道来。
装饰器模式概述
装饰器模式(Decorator Pattern)也叫作包装器模式(Wrapper Pattern),指在不改变原有对象的基础上,动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活,属于结构型设计模式。
英文:
Attach additional responsibilities to an object dynamicallykeeping the same interface.Decorators provide a flexible alternativeto subclassing for extending functionality.
.擴充原有物件的功能)將功能附加到物件上。因此,裝飾器模式的核心是功能擴充。使用裝飾器模式可以透明且動態地擴展類別的功能。
生活中的案例
#一套毛坯房,沒有裝修之前,看起來非常難看,但只要稍微裝潢一番,那就漂亮多了,而且能洗澡、睡覺、做飯等,但本質還是房子。
一輛汽車,原本就是一輛代步的車,但是瑪麗加大,配置提升,然後就成了豪車,但本質還是一輛代步的車。
一個女生,原本很平凡,長相一般,但是經過一番化妝,再穿點好看的衣服,然後就成了很多人心中的女神了。
總之,經過點裝飾後,就是不一樣了,功能增強了。
裝飾器模式通用程式碼實作
#我們還是用程式碼來實現一把,程式設計師都喜歡先搞個demo,然後再慢慢研究。
//抽象组件 public abstract class Component { public abstract void operation(); } //具体组件 public class ConcreteComponent extends Component { @Override public void operation() { System.out.println("ConcreteComponent operation"); } } //装饰器抽象 public abstract class Decorator extends Component { protected Component component; public Decorator(Component component) { this.component = component; } @Override public void operation() { component.operation(); } } //具体装饰器 public class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component); } @Override public void operation() { System.out.println("开始前搞点事"); super.operation(); System.out.println("结束后搞点事"); } } //测试 public class Client { public static void main(String[] args) { Component component = new ConcreteDecorator(new ConcreteComponent()); component.operation(); } }
運行結果:
开始前搞点事 ConcreteComponent operation 结束后搞点事
以上便是裝飾器模式的通用程式碼實現,下面我們來分析一下。
裝飾模式UML圖
#從UML途中可以看出,其中的角色
装饰器模式中的角色
抽象组件(Component):可以是一个接口或者抽象类,充当被装饰类的原始对象,规定了被装饰对象的行为。 具体组件(ConcreteComponent):实现/继承Component的一个具体对象,即被装饰对象。 抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,其内部必然有一个属性指向Component;其实现一般是一个抽象类,主要为了让其子类按照其构造形式传入一个Component,这是强制的通用行为。如果系统中装饰逻辑单一,则并不需要实现许多装饰器,可以直接省略该类,而直接实现一个具体装饰器即可。 具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上,每个ConcreteDecorator都扩展了Component对象的一种功能。
小结
装饰器模式角色分配符合设计模式的里氏替换原则、依赖倒置原则,从而使得其具备很强的扩展性,最终满足开闭原则。
装饰器模式的实现原理是,让装饰器实现与被装饰类(例如ConcreteComponent)相同的接口(例如Component),使得装饰器与被扩展类类型一致,并在构造函数中传入该接口对象,然后在实现这个接口的被包装类对象的现有功能上添加新功能。由于装饰器与被包装类属于同一类型(均为Component),且构造函数的参数为其实现接口类(Component),因此装饰器模式具备嵌套扩展功能,这样就能使用装饰器模式一层一层地对底层被包装类进行功能扩展了。
实战
在实际开发中,都会存在系统与系统之间的调用,假如说我们现在有个支付功能,现在一切都是没问题的,但是 我们此时需要对发起支付前的请求参数和支付后的相应参数。进行统一处理,原功能不变,只是在原功能上做了一点扩展(增强)。
老功能代码如下:
/** * @author 田先生 * @date 2021-06-02 * * 欢迎关注公众号:java后端技术全栈 */ public interface IOrderPayService { String payment(Long orderId, BigDecimal amount); } public class OrderPayServiceImpl implements IOrderPayService { @Override public String payment(Long orderId, BigDecimal amount) { //先调用余额查询是否足够 System.out.println("发起支付,订单号:" + orderId + ", 支付金额:" + amount.toString()); //调用支付系统 String result = "订单id=" + orderId + "支付完成"; System.out.println("支付结果:" + result); return result; } } public class OrderClient { public static void main(String[] args) { IOrderPayService orderPayService = new OrderPayServiceImpl(); orderPayService.payment(10001L,new BigDecimal("5000")); } }
运行输出:
发起支付,订单号:10001, 支付金额:5000 支付结果:订单id=10001支付完成
新需求,需要把这些请求参数和相应结果进行单独搜集处理,此时为了不影响原有功能,于是我们可以对其进行功能增强。
/** * @author 田先生 * @date 2021-06-02 * * 欢迎关注公众号:java后端技术全栈 */ public class OrderPayDecorator implements IOrderPayService { private IOrderPayService orderPayService; public OrderPayDecorator(IOrderPayService orderPayService) { this.orderPayService = orderPayService; } @Override public String payment(Long orderId, BigDecimal amount) { System.out.println("把这个订单信息(发起支付)" + "订单id=" + orderId + "支付金额=" + amount.toString() + " 【发送给MQ】"); String result = orderPayService.payment(orderId, amount); System.out.println("把订单支付结果信息" + result + " 【发送给MQ】"); return result; } } public class OrderClient { public static void main(String[] args) { IOrderPayService orderPayService =new OrderPayDecorator(new OrderPayServiceImpl()); orderPayService.payment(10001L,new BigDecimal("5000")); } }
运行输出:
把这个订单信息(发起支付)订单id=10001支付金额=5000 【发送给MQ】 发起支付,订单号:10001, 支付金额:5000 支付结果:订单id=10001支付完成 把订单支付结果信息订单id=10001支付完成 【发送给MQ】
整个过程,大家有没有发现,我们并没动原有的代码,仅仅只是做了功能增强。
装饰器模式在新项目中基本上不会用到,通常都是在老项目中使用,因为已有的功能不变,只是做了一些功能增强。
大神们是怎么用的
装饰器设计模式在JDK源码、Spring源码以及Mybatis源码中都有。
JDK源码中
装饰器模式比较经典的应用就是 JDK 中的 java.io 包下,InputStream、OuputStream、Reader、Writer 及它们的子类。
以 InputStream 为例
FileInputStream 是 InputStream 的子类,用来读取文件字节流 BufferedInputStream 是 InputStream 的子类的子类,可缓存的字节流 DataInputStream 也是 InputStream 的子类的子类,可直接读取 Java 基本类型的字节流
UML图
DataInputStream 中构造器入参便是自己的父类(InputStream)。
如果希望提供一个可以读取文件 + 可缓存的字节流,使用继承方式,就需要派生 FileBufferedInputStream;
如果希望提供一个可以读取文件 + 直接读取基本类型的字节流,使用继承方式,就需要派生 FileDataInputStream。
字节流功能的增强还包括支持管道 pipe、字节数组 bytearray、字节对象 object、字节流字符流的转换 等维度,如果用继承方式,那类的层级与种类会多到爆炸。
为了解决问题,这边就使用了装饰器模式。
Spring源码中
在Spring中,我们可以尝试理解一下TransactionAwareCacheDecorator类,这个类主要用来处理事务缓存,代码如下。
public class TransactionAwareCacheDecorator implements Cache { private final Cache targetCache; //构造方法入参类型为自己的父类(接口类型) public TransactionAwareCacheDecorator(Cache targetCache) { Assert.notNull(targetCache, "Target Cache must not be null"); this.targetCache = targetCache; } public Cache getTargetCache() { return this.targetCache; } //... }
TransactionAwareCacheDecorator就是对Cache的一个包装,因此,这里也是使用了装饰器模式。
Mybatis源码中
MyBatis中关于Cache和CachingExecutor接口的实现类也使用了装饰者设计模式。Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;CachingExecutor是一个Executor的装饰器,给一个Executor增加了缓存的功能。此时可以看做是对Executor类的一个增强,故使用装饰器模式是合适的。
在CachingExecutor 中
public class CachingExecutor implements Executor { //持有组件对象 private Executor delegate; private TransactionalCacheManager tcm = new TransactionalCacheManager(); //构造方法,传入组件对象 public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { //转发请求给组件对象,可以在转发前后执行一些附加动作 flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } //... }
总结
看完装饰器模式后,你是否有感觉,装饰器模式和代理模式非常的相像,下面我们就来做个对比。
1.装饰器模式可以理解为一种特殊的代理模式。
2.装饰器模式强调自身的功能扩展,透明的扩展(即用户想增强什么功能就增强什么功能),可动态定制的扩展。
3.代理模式强调的是代理过程的控制。
优点
装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态地给一个对象扩展功能,即插即用。 通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果。 装饰器模式完全遵守开闭原则。
缺点
会出现更多的代码、更多的类,增加程序的复杂性。 动态装饰在多层装饰时会更复杂。
以上是3年工作必備 裝飾器模式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undress AI Tool
免費脫衣圖片

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

HashMap在Java中通過哈希表實現鍵值對存儲,其核心在於快速定位數據位置。 1.首先使用鍵的hashCode()方法生成哈希值,並通過位運算轉換為數組索引;2.不同對象可能產生相同哈希值,導致衝突,此時以鍊錶形式掛載節點,JDK8後鍊錶過長(默認長度8)則轉為紅黑樹提升效率;3.使用自定義類作鍵時必須重寫equals()和hashCode()方法;4.HashMap動態擴容,當元素數超過容量乘以負載因子(默認0.75)時,擴容並重新哈希;5.HashMap非線程安全,多線程下應使用Concu

Optional能清晰表達意圖並減少null判斷的代碼噪音。 1.Optional.ofNullable是處理可能為null對象的常用方式,如從map中取值時可結合orElse提供默認值,邏輯更清晰簡潔;2.通過鍊式調用map實現嵌套取值,安全地避免NPE,任一環節為null則自動終止並返回默認值;3.filter可用於條件篩選,滿足條件才繼續執行後續操作,否則直接跳到orElse,適合輕量級業務判斷;4.不建議過度使用Optional,如基本類型或簡單邏輯中其反而增加複雜度,部分場景直接返回nu

處理Java中的字符編碼問題,關鍵是在每一步都明確指定使用的編碼。 1.讀寫文本時始終指定編碼,使用InputStreamReader和OutputStreamWriter並傳入明確的字符集,避免依賴系統默認編碼。 2.在網絡邊界處理字符串時確保兩端一致,設置正確的Content-Type頭並用庫顯式指定編碼。 3.謹慎使用String.getBytes()和newString(byte[]),應始終手動指定StandardCharsets.UTF_8以避免平台差異導致的數據損壞。總之,通過在每個階段

遇到java.io.NotSerializableException的核心解決方法是確保所有需序列化的類實現Serializable接口,並檢查嵌套對象的序列化支持。 1.給主類添加implementsSerializable;2.確保類中自定義字段對應的類也實現Serializable;3.用transient標記不需要序列化的字段;4.檢查集合或嵌套對像中的非序列化類型;5.查看異常信息定位具體哪個類未實現接口;6.對無法修改的類考慮替換設計,如保存關鍵數據或使用可序列化的中間結構;7.考慮改

JavaSocket編程是網絡通信的基礎,通過Socket實現客戶端與服務器間的數據交換。 1.Java中Socket分為客戶端使用的Socket類和服務器端使用的ServerSocket類;2.編寫Socket程序需先啟動服務器監聽端口,再由客戶端發起連接;3.通信過程包括連接建立、數據讀寫及流關閉;4.注意事項包括避免端口衝突、正確配置IP地址、合理關閉資源及支持多客戶端的方法。掌握這些即可實現基本的網絡通信功能。

在Java中,Comparable用於類內部定義默認排序規則,Comparator用於外部靈活定義多種排序邏輯。 1.Comparable是類自身實現的接口,通過重寫compareTo()方法定義自然順序,適用於類有固定、最常用的排序方式,如String或Integer。 2.Comparator是外部定義的函數式接口,通過compare()方法實現,適合同一類需要多種排序方式、無法修改類源碼或排序邏輯經常變化的情況。兩者區別在於Comparable只能定義一種排序邏輯且需修改類本身,而Compar

遍歷Java中的Map有三種常用方法:1.使用entrySet同時獲取鍵和值,適用於大多數場景;2.使用keySet或values分別遍歷鍵或值;3.使用Java8的forEach簡化代碼結構。 entrySet返回包含所有鍵值對的Set集合,每次循環獲取Map.Entry對象,適合頻繁訪問鍵和值的情況;若只需鍵或值,可分別調用keySet()或values(),也可在遍歷鍵時通過map.get(key)獲取值;Java8中可通過Lambda表達式使用forEach((key,value)->

InJava,thestatickeywordmeansamemberbelongstotheclassitself,nottoinstances.Staticvariablesaresharedacrossallinstancesandaccessedwithoutobjectcreation,usefulforglobaltrackingorconstants.Staticmethodsoperateattheclasslevel,cannotaccessnon-staticmembers,
