一、引言
今天我們要講【結構型】設計模式的第三個模式,該模式是【裝飾模式】,英文名稱:Decorator Pattern。我第一次看到這個名稱想到的是另外一個詞語“裝修”,我就說說我對“裝修”的理解吧,大家一定要看清楚,是“裝修”,不是“裝飾”。我們長大了,就要結婚,要結婚就涉及到要買房子,買的精裝修或者簡單裝修就可以住的,暫時不談。我們就談談我們購買的是毛坯房。如果我想要房子的內飾是大理石風格的,我們只要在毛坯房的基礎之上用大理石風格的材料裝修就可以,我們當然不可能為了要一個裝修風格,就把剛剛蓋好的房子拆了在重新來過。房子裝修好了,我們就住了進來,很開心。過了段時間,我們發現我們的房子在冬季比較冷,於是我就想給我們的房子增加保暖的功能,裝修好的房子我們可以繼續居住,我們只是在房子外面加一層保護層就可以了。又過了一段時間,總是有陌生人光顧,所以我們想讓房子更安全,於是我們在外牆和房頂加裝安全攝像頭,同時門窗也增加安全系統。隨着時間的流逝,我們可能會根據我們的需求增加相應的功能,期間,我們的房子可以正常使用,加上什么設施就有了相應的功能。從這一方面來講,“裝修”和“裝飾”有類似的概念,接下來就讓我們看看裝飾模式具體是什么吧!
二、裝飾模式的詳細介紹
2.1、動機(Motivate)
在房子裝修的過程中,各種功能可以相互組合,來增加房子的功用。類似的,如果我們在軟件系統中,要給某個類型或者對象增加功能,如果使用“繼承”的方案來寫代碼,就會出現子類暴漲的情況。比如:IMarbleStyle是大理石風格的一個功能,IKeepWarm是保溫的一個接口定義,IHouseSecurity是房子安全的一個接口,就三個接口來說,House是我們房子,我們的房子要什么功能就實現什么接口,如果房子要的是復合功能,接口不同的組合就有不同的結果,這樣就導致我們子類膨脹嚴重,如果需要在增加功能,子類會成指數增長。這個問題的根源在於我們“過度地使用了繼承來擴展對象的功能”,由於繼承為類型引入的靜態特質(所謂靜態特質,就是說如果想要某種功能,我們必須在編譯的時候就要定義這個類,這也是強類型語言的特點。靜態,就是指在編譯的時候要確定的東西;動態,是指運行時確定的東西),使得這種擴展方式缺乏靈活性;並且隨着子類的增多(擴展功能的增多),各種子類的組合(擴展功能的組合)會導致更多子類的膨脹(多繼承)。如何使“對象功能的擴展”能夠根據需要來動態(即運行時)地實現?同時避免“擴展功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴展變化”所導致的影響降為最低?
2.2、意圖(Intent)
動態地給一個對象增加一些額外的職責。就增加功能而言,Decorator模式比生成子類更為靈活。 —— 《設計模式》GoF
2.3、結構圖(Structure)
2.4、模式的組成
在裝飾模式中的各個角色有:
(1)、抽象構件角色(Component):給出一個抽象接口,以規范准備接收附加責任的對象。
(2)、具體構件角色(Concrete Component):定義一個將要接收附加責任的類。
(3)、裝飾角色(Decorator):持有一個構件(Component)對象的實例,並實現一個與抽象構件接口一致的接口。
(4)、具體裝飾角色(Concrete Decorator):負責給構件對象添加上附加的責任。
2.5 、裝飾模式的具體代碼實現
剛開始一看這個“裝飾模式”是有點不太好理解,既然這個模式是面向對象的設計模式,那在現實生活中一定有事例和其對應,其實這種例子也不少,大家好好的挖掘吧,也可以提高我們對面向對象的理解。我繼續拿蓋房子來說事吧。
1 namespace 裝飾模式的實現 2 { 3 /// <summary> 4 /// 該抽象類就是房子抽象接口的定義,該類型就相當於是Component類型,是餃子餡,需要裝飾的,需要包裝的 5 /// </summary> 6 public abstract class House 7 { 8 //房子的裝修方法--該操作相當於Component類型的Operation方法 9 public abstract void Renovation(); 10 } 11 12 /// <summary> 13 /// 該抽象類就是裝飾接口的定義,該類型就相當於是Decorator類型,如果需要具體的功能,可以子類化該類型 14 /// </summary> 15 public abstract class DecorationStrategy:House //關鍵點之二,體現關系為Is-a,有這這個關系,裝飾的類也可以繼續裝飾了 16 { 17 //通過組合方式引用Decorator類型,該類型實施具體功能的增加 18 //這是關鍵點之一,包含關系,體現為Has-a 19 protected House _house; 20 21 //通過構造器注入,初始化平台實現 22 protected DecorationStrategy(House house) 23 { 24 this._house=house; 25 } 26 27 //該方法就相當於Decorator類型的Operation方法 28 public override void Renovation() 29 { 30 if(this._house!=null) 31 { 32 this._house.Renovation(); 33 } 34 } 35 } 36 37 /// <summary> 38 /// PatrickLiu的房子,我要按我的要求做房子,相當於ConcreteComponent類型,這就是我們具體的餃子餡,我個人比較喜歡韭菜餡 39 /// </summary> 40 public sealed class PatrickLiuHouse:House 41 { 42 public override void Renovation() 43 { 44 Console.WriteLine("裝修PatrickLiu的房子"); 45 } 46 } 47 48 49 /// <summary> 50 /// 具有安全功能的設備,可以提供監視和報警功能,相當於ConcreteDecoratorA類型 51 /// </summary> 52 public sealed class HouseSecurityDecorator:DecorationStrategy 53 { 54 public HouseSecurityDecorator(House house):base(house){} 55 56 public override void Renovation() 57 { 58 base.Renovation(); 59 Console.WriteLine("增加安全系統"); 60 } 61 } 62 63 /// <summary> 64 /// 具有保溫接口的材料,提供保溫功能,相當於ConcreteDecoratorB類型 65 /// </summary> 66 public sealed class KeepWarmDecorator:DecorationStrategy 67 { 68 public KeepWarmDecorator(House house):base(house){} 69 70 public override void Renovation() 71 { 72 base.Renovation(); 73 Console.WriteLine("增加保溫的功能"); 74 } 75 } 76 77 public class Program 78 { 79 static void Main() 80 { 81 //這就是我們的餃子餡,需要裝飾的房子 82 House myselfHouse=new PatrickLiuHouse(); 83 84 DecorationStrategy securityHouse=new HouseSecurityDecorator(myselfHouse); 85 securityHouse.Renovation(); 86 //房子就有了安全系統了 87 88 //如果我既要安全系統又要保暖呢,繼續裝飾就行 89 DecorationStrategy securityAndWarmHouse=new HouseSecurityDecorator(securityHouse); 90 securityAndWarmHouse.Renovation(); 91 } 92 } 93 }
寫了很多備注,大家好好體會一下,里面有兩個關鍵點,仔細把握。
三、裝飾模式的實現要點:
1、通過采用組合、而非繼承的手法,Decorator模式實現了在運行時動態地擴展對象功能的能力,而且可以根據需要擴展多個功能。避免了單獨使用繼承帶來的“靈活性差”和“多子類衍生問題”。
2、Component類在Decorator模式中充當抽象接口的角色,不應該去實現具體的行為。而且Decorator類對於Component類應該透明——換言之Component類無需知道Decorator類,Decorator類是從外部來擴展Component類的功能。
3、Decorator類在接口上表現為is-a Component的繼承關系,即Decorator類繼承了Component類所具有的接口。但在實現上又表現為has-a Component的組合關系,即Decorator類又使用了另外一個Component類。我們可以使用一個或者多個Decorator對象來“裝飾”一個Component對象,且裝飾后的對象仍然是一個Component對象。
4、Decorator模式並非解決“多子類衍生的多繼承”問題,Decorator模式應用的要點在於解決“主體類在多個方向上的擴展功能”——是為“裝飾”的含義。
3.1】、裝飾模式的優點:
(1)、把抽象接口與其實現解耦。
(2)、抽象和實現可以獨立擴展,不會影響到對方。
(3)、實現細節對客戶透明,對用於隱藏了具體實現細節。
3.2】、裝飾模式的缺點:
(1)、增加了系統的復雜度
3.3】、在以下情況下應當使用橋接模式:
(1)、如果一個系統需要在構件的抽象化角色和具體化角色之間添加更多的靈活性,避免在兩個層次之間建立靜態的聯系。
(2)、設計要求實現化角色的任何改變不應當影響客戶端,或者實現化角色的改變對客戶端是完全透明的。
(3)、需要跨越多個平台的圖形和窗口系統上。
(4)、一個類存在兩個獨立變化的維度,且兩個維度都需要進行擴展。
四、.NET 中裝飾模式的實現
在Net框架中,有一個類型很明顯的使用了“裝飾模式”,這個類型就是Stream。Stream類型是一個抽象接口,它在System.IO命名空間里面,它其實就是Component。FileStream、NetworkStream、MemoryStream都是實體類ConcreteComponent。右邊的BufferedStream、CryptoStream是裝飾對象,它們都是繼承了Stream接口的。
如圖:
Stream就相當於Component,定義裝飾的對象,FileStream就是要裝飾的對象,BufferedStream是裝飾對象。我們看看BufferedStream的定義,部分定義了。
1 public sealed class BufferedStream : Stream 2 { 3 private const int _DefaultBufferSize = 4096; 4 5 private Stream _stream;
結構很簡單,對比結構圖看吧,沒什么可說的了。
五、總結
今天的文章就寫到這里了,總結一下我對這個模式的看法,這個模式有點像包餃子,ConcreteComponent其實是餃子餡,Decorator就像餃子皮一樣,包什么皮就有什么的樣子,皮和皮也可以嵌套,當然我們生活中的餃子只是包一層。其實手機也是一個裝飾模式使用的好例子,以前我們的手機只是接打電話,然后可以發短信和彩信,我在裝飾一個就可以拍照了。我們現在的手機功能很豐富,其結果也類似裝飾的結果。隨着社會的進步,技術發展,模塊化的手機也出現了,其設計原理也和“裝飾模式”就更接近了。不光手機,還有我們身邊其他很多家用電器也有類似的發展經歷,我們努力發現生活中的真理吧,然后再在軟件環境中慢慢體會吧。