手繪設計模式結構圖

GoF的設計模式一共23個,可以分為3大類:創建型、結構型和行為型,這篇文章主要討論創建型。

創建型的設計模式包括:簡單工廠(Simple Factory)、工廠方法(Factory Method)、抽象工廠(Abstract Factory)、單例(Singleton)、構造者(Builder)和原型(Prototype),我們分別來討論。

我們首先來看工廠系列的3個設計模式,它們都主要是針對軟件設計中的“開放-封閉”原則,即程序應該對擴展開放,對修改封閉。特別是當我們的程序采用XML+反射的方式來創建對象時,工廠模式的威力就完全展現出來了,這時我們可以通過維護配置文件的方式,來控制程序的邏輯。

1)簡單工廠,當我們的程序在實例化對象時,如果輸入條件不一樣,產生的對象也不一樣,那么我們可以考慮使用簡單工廠對不同的實例進行統一封裝, UML結構如下:

優點:封裝了具體對象的實例化過程,Client端和具體對象解耦,同時ProductManager可以作成靜態類或者Singleton對象,然后可以使用HashMap緩存具體對象(前提是對象沒有時間依賴性),降低創建對象的次數。

缺點:當增添一種新類型的對象時,需要修改Productmanager的代碼(如果不采用XML)

2)工廠方法,它是針對簡單工廠的改進版,添加了對ProductManager的抽象,UML結構如下:

優點:結構更加靈活,對于某種類型的對象來說,會有一個特定的對象工廠指向它,這樣當我們需要添加一種新類型的產品時,只需要添加兩個類,一個是具體產品類,一個是新產品的工廠類。這樣更加靈活。

缺點:結構開始變得復雜,而且最終還是需要Client端來確定究竟使用哪一個Factory(當然這個信息可以保存在上下文或者配置文件中)。

3)抽象工廠,這個是最復雜的工廠模式,它用來生成一個產品線上的所有產品,我們假設一個產品線上包括多個產品,不同的產品線上的產品個數是一樣的,這樣我們需要一個針對產品線的抽象,并且很顯然不同產品線上的產品是不可能混到一起的。對應的UML結構圖如下:

上圖表明,一個產品線上的產品由IProduct1和IProduct2組成,客戶端在獲取產品時,這兩個產品應該是同時返回的,因此對于IProductManager來說,它需要同時生成這兩個對象。

優點:對創建產品家族的行為高度抽象,添加一個產品線的邏輯比較清晰。

缺點:當我們對產品線上的產品進行增加和刪除時,對應的操作比較麻煩,所有的產品工廠都需要進行修改。

4)單例,這是比較好理解的一個模式,從字面上說,就是程序在運行的過程中,希望在任意時刻,都只保留某個對象的唯一實例。對應的UML結構圖如下:

單例的實現方式一般包括幾步:1)私有的指向自身的字段;2)私有構造函數;3)公開對私有字段進行實例化的方法。也有幾種針對具體語言進行的改善,例如針對多線程采用double lock機制,采用常量方式定義私有字段、使用內嵌類來實例化字段等。

我們也可以對單例進行一些適當的擴展,例如我們將對象的個數由1個變為N個,這就成了對象池。

通常工廠模式中會使用到單例模式,特別是對于簡單工廠來說。

5)構造者,對于一些復雜對象來說,它可以分成多個不同的部分,在實例化時,不同部分之間實例化的順序,有時會有嚴格的限制,這時我們就可以使用構造者模式了。對應的UML結構圖如下:

我們定義了IBuilder接口來實例化對應的不同部分,同時有一個方法來返回對象的實例。而Constructor類的Construct方法會按照業務邏輯依次調用實例化部分對象的方法,即BuildPartA、BuildPartB,這里的調用順序,完全由業務邏輯來控制,最后可以調用GetProduct方法取得完整的對象實例。

我們有時也會對上圖進行修改,例如將GetProduct放到Constructor中,或者將Construct方法放入到GetProduct(取消Constructor)中。即使有這些變形,但是基本的思想是不變的。

6)原型,我們在程序運行過程中,當需要有新的實例對象時,有時并不希望是從頭創建一個對象,而是希望新的實例的狀態和某個已存在的實例保持一致,這就是原型模式發揮作用的地方。對應的UML結構圖如下:

在.NET中,已經定義了IClonable接口來實現原型模式。需要注意在實現時,會有深拷貝和淺拷貝的區別,深拷貝會同時拷貝堆棧和堆上的內容,而淺拷貝只會拷貝堆棧上的內容。

在這部分里,我們關注GoF里面的結構型模式,它主要是用于描述如何將類組合在一起去構成更大的結構。結構型模式包括適配器(Adapter)、裝飾(Decorator)、橋接器(Bridge)、享元(FlyWeight)、門面(Facade)、合成(Composite)以及代理(Proxy)模式。

下面我們對上面提到的模式分別進行描述。

1)適配器(Adapter)。當我們已經開發出一個模塊,有一套清晰的接口,并且模塊正在被某個功能使用(意味著模塊接口改變的可能性不高),這是如果有另外一個功能也需要使用這個模塊的功能,但是對應的是一套完全不同的接口,這時適配器就可以發揮作用了。

適配器模式分為兩種,一種是對象適配器,一種是類適配器,對象適配器的UML圖如下:

這里Adaptee1和Adaptee2指兩套不同的子系統,它們作為Adapter的屬性存在,可以使用IoC的方式指定。

類適配器的UML圖如下:

同樣是兩個不同的子系統,但是這里我們創建了2個Adapter類來分別指向兩個子系統。在這里我們可以在Client和ITarget之間,設置一個Adapter工廠,來根據業務需求創建不同的Adpater實例。

2)裝飾(Decorator),假如我們已經開發了一套功能,然后根據需求,需要增加一些子功能,而且這些子功能是比較分散比較時可以增刪的,這時如果直接修改接口,那么會造成接口功能復雜并且不穩定,針對這種情況,我們可以使用裝飾模式。對應的UML圖如下:

上圖中,ConcreteComponent已經實現了Component的基本功能,對于一些附加的功能,如果放在ConcreteComponent中不合適的話,我們可以像ConcreteDecoratorA一樣,創建一個基于Decorator的類,通過SetComponent方法將核心功能和輔助功能串在一起。

有時,為了簡單,我們也可以把ConcreteDecorator直接掛在Concretecomponent下面。

3)橋接器(Bridge),面向對象提倡的幾個最佳實踐包括:1)封裝變化;2)面向接口編程;3)組合優于繼承;4)類的職責盡量單一。橋接器完美的體現了這些,通過創建型模式,我們可以很好地達到面向接口編程的目標,也就是說我們在程序中各變量的聲明類型是接口類型或者抽象類,而具體的實現類型則由不同的設計模式使用不同方式指定。這在接口或者抽象類基本穩定的情況下,是很好地,但當接口需要發生變化時,我們如何去處理?可以看看橋接器的UML圖:

通過這個圖,我們可以看出,Implementor接口的變化,對于Client來說,基本是沒有影響的。Abstraction會持有Implementor的一個實例。

4)享元(FlyWeight),當我們系統中需要使用大量的小對象,但我們又不希望將所有的小對象都創建出來時,可以考慮使用享元模式,它會抽取小對象中的公共部分,將其封裝為基類,然后針對不同條件創建小對象,同時在對象池中維護這些小對象,客戶在需要使用小對象時,首先在對象池中查找,如果存在,直接返回。對于小對象中“個性”的部分,由調用小對象的客戶端進行維護。對應的UML圖如下:

除了上述的簡單享元,還存在一種復合享元,對應的UML圖如下:

圖中,CompositeConcreteComponent是不共享的,但是它里面包含很多簡單的享元,這些享元是共享的,我們可以把它想象成一個特殊的“享元工廠”。

通常提到享元,最常見的例子就是文本編輯器中的26個字母,在.NET中,字符串常量也使用了享元模式。

在享元模式中,我們通常會將FlyWeightFactory設計為單例模式,否則享元就沒有意義了。

5)門面(Facade),如果我們的程序需要深入調用某個模塊的內部,但我們又不想和模塊過緊耦合,這時可以考慮使用門面模式,來對外部封裝內部子系統的實現。簡單的門面可能和代理在某種程度上很相似。

門面模式沒有固定的UML圖,它是根據客戶端的實際需求以及子系統內部的接口來確定的。

6)合成(Composite),當我們的對象結構中存在“父子”關系時,可以考慮使用合成模式。它分為兩種,一種是安全型的合成模式,UML圖如下:

這種類型的合成模式,對于Component的增、刪、改,都在Composite中維護,Leaf根本不知道這些操作。另一種是透明型的合成模式,UML圖如下:

這種類型的合成模式,自上而下所有的Component都會有增、刪、改的操作,只不過對于Leaf來說,這些操作時沒有意義的。

7)代理(Proxy),在編寫程序時,有時我們希望使用某個對象或者模塊的功能,但是因為種種原因,我們不能直接訪問,這時就可以考慮使用代理,對應的UML圖如下:

需要注意的是,在這里RealSubject只有一個,如果有多個,那么就是Adapter了。另外,代理也可以加入自己的一些邏輯處理,例如PreExecute和PostExecute。如果這里有多個Proxy,那么就是Decorator了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容

  • 設計模式匯總 一、基礎知識 1. 設計模式概述 定義:設計模式(Design Pattern)是一套被反復使用、多...
    MinoyJet閱讀 3,960評論 1 15
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,729評論 18 399
  • 一、設計模式的分類 總體來說設計模式分為三大類: 創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者...
    RamboLI閱讀 767評論 0 1
  • 一個UML類圖 類之間的關系 類的繼承結構表現在UML中為:泛化(generalize)與實現(realize) ...
    僚機KK閱讀 656評論 0 0
  • 本文首發于個人博客:Lam's Blog - 談談23種設計模式在Android源碼及項目中的應用,文章由Mark...
    格子林ll閱讀 4,671評論 1 105