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.代理模式強調的是代理過程的控制。
優點
- 裝飾器是繼承的有力補充,比繼承靈活,在不改變原有對象的情況下,動態地給一個對象擴展功能,即插即用。
- 通過使用不同裝飾類及這些裝飾類的排列組合,可以實現不同效果。
- 裝飾器模式完全遵守開閉原則。
缺點
- 會出現更多的代碼、更多的類,增加程序的復雜性。
- 動態裝飾在多層裝飾時會更復雜。
- 好了,今天的分享就到此結束,希望你能徹底掌握裝飾器模式,如果還有疑問,或者技術探討之類的,歡迎加我微信,一起探討。
文轉載自微信公眾號「Java后端技術全?!?,可以通過以下二維碼關注。轉載本文請聯系Java后端技術全棧公眾號。