前言
只有光頭才能變強,今天要講解的就是
裝飾模式 啦~在看到 FilterInputStream
和 FilterOutputStream
時看到了之前常聽見的 裝飾模式 (對IO一定了解的同學可能都會知道那麼一句話:在IO用得最多的就是裝飾模式了)!
其實無論是代理模式還是裝飾模式。本質上我認為就是 對原有對象增強的方式 ~
那麼接下來就開始吧,如果文章有錯誤的地方請大家多多包涵,不吝在評論區指正哦~
一、對象增強的常用方式
很多時候我們可能 對Java提供給我們的對象不滿意 ,不能滿足我們的功能。此時我們就想對Java原對象進行增強,能夠實現我們想要的功能就好~
一般來說,實現對象增強有三種方式:
繼什承
繼承父類,子類擴展
裝飾器模式
使用“包裝”的方式來增強對象
代理模式
給女朋友講解麼是代理模式
1.1繼承
最簡單的方式就是繼承父類,子類擴展來達到目的。雖然簡單,但是這種方式的 缺陷非常大 :
一、如果 父類是帶有數據、信息、屬性的話,那麼子類無法增強。
二、子類實現了之後 需求無法變更 ,增強的內容是 固定 的。
1.1.1第一點
第一點就拿以前在學JDBC的時候來說:
當時想要自己寫一個簡易的JDBC連接池,連接池由
List
來管理。顯然我們的對象是Connection,當寫到close()
方法的時候卡住了。因為我們想要的功能是:調用
close()
是讓我們的Connection返回到“連接池”(集合)中,而不是關閉掉。此時我們 不能 使用繼承父類的方式來實現增強。因為Connection對象是由數據庫廠商來實現的,在得到Connection對象的時候綁定了各種信息(數據庫的username、password、具體的數據庫是啥等等)。我們 子類繼承Connection是無法得到對應的數據的 !就更別說調用
close()
方法了。
1.1.2第二點
第二點我也舉個例子:
現在我設計一個電話類:
public class Phone { // 可以打電話public void call() {System.out.println("打電話給周圍的人關注公眾號Java3y");}}
此時,我想 打電話之前能聽彩鈴 ,於是我繼承Phone類,實現我想要的功能。
public class MusicPhone extends Phone {// 聽彩鈴public void listenMusic() { System.out.println("我懷念的是無話不說,我懷念的是一起做夢~~~~~~");} @Overridepublic void call() { // 在打電話之前聽彩鈴listenMusic(); super.call();}}
我們的功能就做好了:
此時,我又突然想實現多一個需求了,我想要聽完電話之後告訴我一下當前的時間是多少。沒事,我們 又繼承來增強 一下:
// 這裡繼承的是MusicPhone類public class GiveCurrentTimePhone extends MusicPhone { // 給出當前的時間public void currentTime() { System.out.println("當前的時間是:" + System.currentTimeMillis());} @Overridepublic void call() { super.call(); // 打完電話提示現在的時間是多少啦currentTime();}}
所以我們還是可以完成任務滴:
可是我需求現在 又想變 了:
我不想聽彩鈴了,只想聽完電話通知一下時間就好了........(可是我們的通知時間電話類是繼承在聽彩鈴的電話類基礎之上的),,,
我又有可能:我想在聽電話之前報告一下時間,聽完電話聽音樂!...
如果 需求變動很大 的情況下,而我們又用 繼承的方式來實現 這樣會導致一種現象: 類爆炸(類數量激增)!並且 繼承的層次可能會比較多 ~
所以,我們可以看到子類繼承父類這種方式來擴展是 十分侷限的,不靈活的 ~
因此我們就有了
裝飾模式 !1.2裝飾模式
首先我們來看看裝飾模式是怎麼用的吧。
1.2.1前提代碼
電話接口:
// 一個良好的設計是抽取成接口或者抽象類的public interface Phone { // 可以打電話void call();}
具體的實現:
public class IphoneX implements Phone { @Overridepublic void call() {System.out.println("打電話給周圍的人關注公眾號Java3y");}}
1.2.2包裝模式實現
上面我們已經 擁有了一個接口還有一個默認實現 。包裝模式是這樣乾的:
首先我們弄一個 裝飾器 ,它實現了接口,以 組合 的方式接收我們的 默認實現類 。
// 裝飾器,實現接口public abstract class PhoneDecorate implements Phone { // 以組合的方式來獲取默認實現類private Phone phone; public PhoneDecorate(Phone phone) { this.phone = phone;} @Overridepublic void call() {phone.call();}}
有了裝飾器以後,我們的擴展都可以 以裝飾器為基礎進行擴展 ,繼承裝飾器來擴展就好了!
我們想要在 打電話之前聽音樂 :
// 繼承著裝飾器來擴展public class MusicPhone extends PhoneDecorate {public MusicPhone(Phone phone) { super(phone);} // 定義想要擴展的功能public void listenMusic() { System.out.println("繼續跑 帶著赤子的驕傲,生命的閃耀不堅持到底怎能看到,與其苟延殘喘不如縱情燃燒");} // 重寫打電話的方法@Overridepublic void call() { // 在打電話之前聽音樂listenMusic(); super.call();}}
現在我也想在 打完電話後通知當前的時間 ,於是我們也 繼承裝飾類來擴展 :
// 這裡繼承的是MusicPhone裝飾器類public class GiveCurrentTimePhone extends PhoneDecorate {public GiveCurrentTimePhone(Phone phone) { super(phone);} // 自定義想要實現的功能:給出當前的時間public void currentTime() { System.out.println("當前的時間是:" + System.currentTimeMillis());} // 重寫要增強的方法@Overridepublic void call() { super.call(); // 打完電話後通知一下當前時間currentTime();}}
可以完成任務:
就
目前 這樣看起來,比我直接繼承父類要麻煩,而功能效果是一樣的....我們繼續往下看~~此時,我不想在打電話之前聽到彩鈴了,很簡單:我們 不裝飾 它就好了!
此時,我想在打電話前報告一下時間,在打完電話之後聽彩鈴。
注意:雖然說要改動類中的代碼,
但是 這種改動是合理的。因為我定義出的GiveCurrentTimePhone類
和MusicPhone類
本身從語義上就 沒有規定 擴展功能的執行順序而繼承不一樣:先繼承Phone->實現MusicPhone->再繼承MusicPhone實現GiveCurrentTimePhone。這是 固定 的,從繼承的邏輯上已經 寫死了 具體的代碼,是 難以改變 的。
所以我們還是可以很簡單地完成功能:
二、裝飾模式講解
可能有的同學在看完上面的代碼之後,還是迷迷糊糊地不知道裝飾模式是怎麼實現“裝飾”的。下面我就再來解析一下:
第一步:我們有一個Phone接口,該接口定義了Phone的功能
第二步:我們有一個最簡單的實現類iPhoneX
第三步:寫一個裝飾器抽象類PhoneDecorate,以 組合 (構造函數傳遞)的方式接收我們最簡單的實現類iPhoneX。其實裝飾器抽象類的作用就是 代理 (核心的功能還是由最簡單的實現類iPhoneX來做,只不過在 擴展 的時候可以 添加一些沒有的功能而已 )。
第四步:想要擴展什麼功能,就繼承PhoneDecorate裝飾器抽象類,將想要增強的對象(最簡單的實現類iPhoneX或者已經被增強過的對象)傳進去,完成我們的擴展!
再來看看下面的圖,就懂了!
往往我們的代碼可以省略起來,成了這個樣子( 是不是和IO的非常像 !)
// 先增強聽音樂的功能,再增強通知時間的功能Phone phone = new GiveCurrentTimePhone(new MusicPhone(new IphoneX()));
結果是一樣的:
2.1裝飾模式的優缺點
優點:
裝飾類和被裝飾類是可以 獨立 的,低耦合的。互相都不用知道對方的存在
裝飾模式是繼承的一種 替代 方案, 無論包裝多少層,返回的對象都是is-a的關係 (上面的例子:包裝完還是Phone類型)。
實現動態擴展,只要 繼承了裝飾器 就可以 動態 擴展想要的功能了。
缺點:
多層裝飾是比較複雜的,提高了系統的複雜度。不利於我們調試~
三、總結
最後來補充一下包裝模式和代理模式的類圖:
對象增強的三種方式:
繼承
包裝模式
代理模式
那麼只要遇到Java提供給我們的API不夠用,我們增強一下就行了。在寫代碼時,某個類被寫死了,功能不夠用,增強一下就可以了!
理解包裝模式,接下來就開始IO之旅咯~~~
覺得有收穫的話 可以關注收藏轉發一波!大家也可以加 java 高級架構群:725633148 裡面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多!
閱讀更多 JAVA技術程序員 的文章