設計模式(六):裝飾器模式
俄羅斯套娃大家都玩過吧,就像是這樣

這玩意玩起來很上頭,打開一個總期待會有下一個,充滿了趣味性
程序員在寫代碼時,也會遇到像套娃這樣令人上頭的代碼
打開一個類,里面還有一個類,再打開一個,里面還有一個...
這種套娃似的代碼其實是一種很常見的設計模式,它叫裝飾器模式
今天我們就來扒一扒裝飾器模式到底是個什么東西
實際案例
假如我們要寫一個支付的功能,支付的方式有支付寶和微信
我們用代碼來實現這個功能
首先我們要定義一個接口類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類的修改邏輯如下
- 實現
Payment接口類,并重寫pay()和query()兩個方法 - 把
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提供實例化的機會
總結一下就是賬單類里面要給Payment和RefundDecorator兩個類提供實例化的機會
因為RefundDecorator類中已經給Payment提供了實例化方法,所以我們只需要在賬單類里面給RefundDecorator類提供實例化方法就可以了
ps:上面這段邏輯稍微有點繞,但是并不復雜,都是面向對象編程中繼承的一些基礎知識點,細讀幾遍很容易理解的
所以,賬單類的代碼應該這樣寫

這樣我們就完成了對賬功能,其實這個賬單類也是一個裝飾器
在需要對賬的時候可以這樣調用
BillDecorator billDecorator = new BillDecorator(
new RefundDecorator(
new AliPayment()));
billDecorator.pay("赫連小伍測試支付寶支付");
billDecorator.query("赫連小伍測試支付寶查詢");
billDecorator.refund("赫連小伍測試支付寶退款");
billDecorator.check("赫連小伍測試支付寶對賬");
上面的代碼在new對象的時候是不是像極了套娃
如果后期需求一直不斷增加,我們的代碼可以一直套娃下去
套娃一時爽,一直套娃一直爽
總結
裝飾器模式屬于設計模式三大類型中的結構型設計模式,它的主要作用是通過將父類對象作為子類對象的屬性從而給父類對象綁定新的行為
在你希望增加原有對象的行為,而又不想要修改原有對象時,可以考慮使用裝飾器模式
裝飾器模式的實現步驟總共有兩步:
- 提供一個新的類定義需要擴展的方法,并使這個類繼承或實現原有對象,讓其擁有原有對象的所有行為
- 將原有對象作為新定義的類的屬性,并為其提供實例化的方法
優點
- 無需修改原有對象即可擴展它的行為
- 可以通過多個裝飾器為對象綁定不同的行為,使對象的使用更靈活
缺點
- 裝飾器的層級遞進關系比較明顯,想要實現不受順序影響的裝飾器比較困難
- 裝飾器層級比較深時,調用時
套娃比較多,代碼看起來不美觀
與適配器模式比較
裝飾器在可以不改變對象接口的前提下實現對象功能的擴展,適配器模式有時還需要修改接口
裝飾器可以遞歸實現對象行為的多層綁定,適配器不能遞歸
裝飾器主要用來擴展對象的功能,適配器主要為對象原有的功能提供更多的使用場景
還是那句話,學會設計模式不是目的,理解設計模式隱含的設計思想才能無往不利
技術需要沉淀,我們下期再見
-- 以上內容來自公眾號 赫連小伍 ,轉載請注明出處
浙公網安備 33010602011771號