GoF的設(shè)計(jì)模式一共23個(gè),可以分為3大類:創(chuàng)建型、結(jié)構(gòu)型和行為型,這篇文章主要討論創(chuàng)建型。
創(chuàng)建型的設(shè)計(jì)模式包括:簡(jiǎn)單工廠(Simple Factory)、工廠方法(Factory Method)、抽象工廠(Abstract Factory)、單例(Singleton)、構(gòu)造者(Builder)和原型(Prototype),我們分別來(lái)討論。
我們首先來(lái)看工廠系列的3個(gè)設(shè)計(jì)模式,它們都主要是針對(duì)軟件設(shè)計(jì)中的“開(kāi)放-封閉”原則,即程序應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉。特別是當(dāng)我們的程序采用XML+反射的方式來(lái)創(chuàng)建對(duì)象時(shí),工廠模式的威力就完全展現(xiàn)出來(lái)了,這時(shí)我們可以通過(guò)維護(hù)配置文件的方式,來(lái)控制程序的邏輯。
1)簡(jiǎn)單工廠,當(dāng)我們的程序在實(shí)例化對(duì)象時(shí),如果輸入條件不一樣,產(chǎn)生的對(duì)象也不一樣,那么我們可以考慮使用簡(jiǎn)單工廠對(duì)不同的實(shí)例進(jìn)行統(tǒng)一封裝, UML結(jié)構(gòu)如下:

優(yōu)點(diǎn):封裝了具體對(duì)象的實(shí)例化過(guò)程,Client端和具體對(duì)象解耦,同時(shí)ProductManager可以作成靜態(tài)類或者Singleton對(duì)象,然后可以使用HashMap緩存具體對(duì)象(前提是對(duì)象沒(méi)有時(shí)間依賴性),降低創(chuàng)建對(duì)象的次數(shù)。
缺點(diǎn):當(dāng)增添一種新類型的對(duì)象時(shí),需要修改Productmanager的代碼(如果不采用XML)
2)工廠方法,它是針對(duì)簡(jiǎn)單工廠的改進(jìn)版,添加了對(duì)ProductManager的抽象,UML結(jié)構(gòu)如下:

優(yōu)點(diǎn):結(jié)構(gòu)更加靈活,對(duì)于某種類型的對(duì)象來(lái)說(shuō),會(huì)有一個(gè)特定的對(duì)象工廠指向它,這樣當(dāng)我們需要添加一種新類型的產(chǎn)品時(shí),只需要添加兩個(gè)類,一個(gè)是具體產(chǎn)品類,一個(gè)是新產(chǎn)品的工廠類。這樣更加靈活。
缺點(diǎn):結(jié)構(gòu)開(kāi)始變得復(fù)雜,而且最終還是需要Client端來(lái)確定究竟使用哪一個(gè)Factory(當(dāng)然這個(gè)信息可以保存在上下文或者配置文件中)。
3)抽象工廠,這個(gè)是最復(fù)雜的工廠模式,它用來(lái)生成一個(gè)產(chǎn)品線上的所有產(chǎn)品,我們假設(shè)一個(gè)產(chǎn)品線上包括多個(gè)產(chǎn)品,不同的產(chǎn)品線上的產(chǎn)品個(gè)數(shù)是一樣的,這樣我們需要一個(gè)針對(duì)產(chǎn)品線的抽象,并且很顯然不同產(chǎn)品線上的產(chǎn)品是不可能混到一起的。對(duì)應(yīng)的UML結(jié)構(gòu)圖如下:

上圖表明,一個(gè)產(chǎn)品線上的產(chǎn)品由IProduct1和IProduct2組成,客戶端在獲取產(chǎn)品時(shí),這兩個(gè)產(chǎn)品應(yīng)該是同時(shí)返回的,因此對(duì)于IProductManager來(lái)說(shuō),它需要同時(shí)生成這兩個(gè)對(duì)象。
優(yōu)點(diǎn):對(duì)創(chuàng)建產(chǎn)品家族的行為高度抽象,添加一個(gè)產(chǎn)品線的邏輯比較清晰。
缺點(diǎn):當(dāng)我們對(duì)產(chǎn)品線上的產(chǎn)品進(jìn)行增加和刪除時(shí),對(duì)應(yīng)的操作比較麻煩,所有的產(chǎn)品工廠都需要進(jìn)行修改。
4)單例,這是比較好理解的一個(gè)模式,從字面上說(shuō),就是程序在運(yùn)行的過(guò)程中,希望在任意時(shí)刻,都只保留某個(gè)對(duì)象的唯一實(shí)例。對(duì)應(yīng)的UML結(jié)構(gòu)圖如下:

單例的實(shí)現(xiàn)方式一般包括幾步:1)私有的指向自身的字段;2)私有構(gòu)造函數(shù);3)公開(kāi)對(duì)私有字段進(jìn)行實(shí)例化的方法。也有幾種針對(duì)具體語(yǔ)言進(jìn)行的改善,例如針對(duì)多線程采用double lock機(jī)制,采用常量方式定義私有字段、使用內(nèi)嵌類來(lái)實(shí)例化字段等。
我們也可以對(duì)單例進(jìn)行一些適當(dāng)?shù)臄U(kuò)展,例如我們將對(duì)象的個(gè)數(shù)由1個(gè)變?yōu)镹個(gè),這就成了對(duì)象池。
通常工廠模式中會(huì)使用到單例模式,特別是對(duì)于簡(jiǎn)單工廠來(lái)說(shuō)。
5)構(gòu)造者,對(duì)于一些復(fù)雜對(duì)象來(lái)說(shuō),它可以分成多個(gè)不同的部分,在實(shí)例化時(shí),不同部分之間實(shí)例化的順序,有時(shí)會(huì)有嚴(yán)格的限制,這時(shí)我們就可以使用構(gòu)造者模式了。對(duì)應(yīng)的UML結(jié)構(gòu)圖如下:

我們定義了IBuilder接口來(lái)實(shí)例化對(duì)應(yīng)的不同部分,同時(shí)有一個(gè)方法來(lái)返回對(duì)象的實(shí)例。而Constructor類的Construct方法會(huì)按照業(yè)務(wù)邏輯依次調(diào)用實(shí)例化部分對(duì)象的方法,即BuildPartA、BuildPartB,這里的調(diào)用順序,完全由業(yè)務(wù)邏輯來(lái)控制,最后可以調(diào)用GetProduct方法取得完整的對(duì)象實(shí)例。
我們有時(shí)也會(huì)對(duì)上圖進(jìn)行修改,例如將GetProduct放到Constructor中,或者將Construct方法放入到GetProduct(取消Constructor)中。即使有這些變形,但是基本的思想是不變的。
6)原型,我們?cè)诔绦蜻\(yùn)行過(guò)程中,當(dāng)需要有新的實(shí)例對(duì)象時(shí),有時(shí)并不希望是從頭創(chuàng)建一個(gè)對(duì)象,而是希望新的實(shí)例的狀態(tài)和某個(gè)已存在的實(shí)例保持一致,這就是原型模式發(fā)揮作用的地方。對(duì)應(yīng)的UML結(jié)構(gòu)圖如下:

在.NET中,已經(jīng)定義了IClonable接口來(lái)實(shí)現(xiàn)原型模式。需要注意在實(shí)現(xiàn)時(shí),會(huì)有深拷貝和淺拷貝的區(qū)別,深拷貝會(huì)同時(shí)拷貝堆棧和堆上的內(nèi)容,而淺拷貝只會(huì)拷貝堆棧上的內(nèi)容。
在這部分里,我們關(guān)注GoF里面的結(jié)構(gòu)型模式,它主要是用于描述如何將類組合在一起去構(gòu)成更大的結(jié)構(gòu)。結(jié)構(gòu)型模式包括適配器(Adapter)、裝飾(Decorator)、橋接器(Bridge)、享元(FlyWeight)、門(mén)面(Facade)、合成(Composite)以及代理(Proxy)模式。
下面我們對(duì)上面提到的模式分別進(jìn)行描述。
1)適配器(Adapter)。當(dāng)我們已經(jīng)開(kāi)發(fā)出一個(gè)模塊,有一套清晰的接口,并且模塊正在被某個(gè)功能使用(意味著模塊接口改變的可能性不高),這是如果有另外一個(gè)功能也需要使用這個(gè)模塊的功能,但是對(duì)應(yīng)的是一套完全不同的接口,這時(shí)適配器就可以發(fā)揮作用了。
適配器模式分為兩種,一種是對(duì)象適配器,一種是類適配器,對(duì)象適配器的UML圖如下:

這里Adaptee1和Adaptee2指兩套不同的子系統(tǒng),它們作為Adapter的屬性存在,可以使用IoC的方式指定。
類適配器的UML圖如下:

同樣是兩個(gè)不同的子系統(tǒng),但是這里我們創(chuàng)建了2個(gè)Adapter類來(lái)分別指向兩個(gè)子系統(tǒng)。在這里我們可以在Client和ITarget之間,設(shè)置一個(gè)Adapter工廠,來(lái)根據(jù)業(yè)務(wù)需求創(chuàng)建不同的Adpater實(shí)例。
2)裝飾(Decorator),假如我們已經(jīng)開(kāi)發(fā)了一套功能,然后根據(jù)需求,需要增加一些子功能,而且這些子功能是比較分散比較時(shí)可以增刪的,這時(shí)如果直接修改接口,那么會(huì)造成接口功能復(fù)雜并且不穩(wěn)定,針對(duì)這種情況,我們可以使用裝飾模式。對(duì)應(yīng)的UML圖如下:

上圖中,ConcreteComponent已經(jīng)實(shí)現(xiàn)了Component的基本功能,對(duì)于一些附加的功能,如果放在ConcreteComponent中不合適的話,我們可以像ConcreteDecoratorA一樣,創(chuàng)建一個(gè)基于Decorator的類,通過(guò)SetComponent方法將核心功能和輔助功能串在一起。
有時(shí),為了簡(jiǎn)單,我們也可以把ConcreteDecorator直接掛在Concretecomponent下面。
3)橋接器(Bridge),面向?qū)ο筇岢膸讉€(gè)最佳實(shí)踐包括:1)封裝變化;2)面向接口編程;3)組合優(yōu)于繼承;4)類的職責(zé)盡量單一。橋接器完美的體現(xiàn)了這些,通過(guò)創(chuàng)建型模式,我們可以很好地達(dá)到面向接口編程的目標(biāo),也就是說(shuō)我們?cè)诔绦蛑懈髯兞康穆暶黝愋褪墙涌陬愋突蛘叱橄箢?,而具體的實(shí)現(xiàn)類型則由不同的設(shè)計(jì)模式使用不同方式指定。這在接口或者抽象類基本穩(wěn)定的情況下,是很好地,但當(dāng)接口需要發(fā)生變化時(shí),我們?nèi)绾稳ヌ幚??可以看看橋接器的UML圖:

通過(guò)這個(gè)圖,我們可以看出,Implementor接口的變化,對(duì)于Client來(lái)說(shuō),基本是沒(méi)有影響的。Abstraction會(huì)持有Implementor的一個(gè)實(shí)例。
4)享元(FlyWeight),當(dāng)我們系統(tǒng)中需要使用大量的小對(duì)象,但我們又不希望將所有的小對(duì)象都創(chuàng)建出來(lái)時(shí),可以考慮使用享元模式,它會(huì)抽取小對(duì)象中的公共部分,將其封裝為基類,然后針對(duì)不同條件創(chuàng)建小對(duì)象,同時(shí)在對(duì)象池中維護(hù)這些小對(duì)象,客戶在需要使用小對(duì)象時(shí),首先在對(duì)象池中查找,如果存在,直接返回。對(duì)于小對(duì)象中“個(gè)性”的部分,由調(diào)用小對(duì)象的客戶端進(jìn)行維護(hù)。對(duì)應(yīng)的UML圖如下:

除了上述的簡(jiǎn)單享元,還存在一種復(fù)合享元,對(duì)應(yīng)的UML圖如下:

圖中,CompositeConcreteComponent是不共享的,但是它里面包含很多簡(jiǎn)單的享元,這些享元是共享的,我們可以把它想象成一個(gè)特殊的“享元工廠”。
通常提到享元,最常見(jiàn)的例子就是文本編輯器中的26個(gè)字母,在.NET中,字符串常量也使用了享元模式。
在享元模式中,我們通常會(huì)將FlyWeightFactory設(shè)計(jì)為單例模式,否則享元就沒(méi)有意義了。
5)門(mén)面(Facade),如果我們的程序需要深入調(diào)用某個(gè)模塊的內(nèi)部,但我們又不想和模塊過(guò)緊耦合,這時(shí)可以考慮使用門(mén)面模式,來(lái)對(duì)外部封裝內(nèi)部子系統(tǒng)的實(shí)現(xiàn)。簡(jiǎn)單的門(mén)面可能和代理在某種程度上很相似。
門(mén)面模式?jīng)]有固定的UML圖,它是根據(jù)客戶端的實(shí)際需求以及子系統(tǒng)內(nèi)部的接口來(lái)確定的。
6)合成(Composite),當(dāng)我們的對(duì)象結(jié)構(gòu)中存在“父子”關(guān)系時(shí),可以考慮使用合成模式。它分為兩種,一種是安全型的合成模式,UML圖如下:

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

這種類型的合成模式,自上而下所有的Component都會(huì)有增、刪、改的操作,只不過(guò)對(duì)于Leaf來(lái)說(shuō),這些操作時(shí)沒(méi)有意義的。
7)代理(Proxy),在編寫(xiě)程序時(shí),有時(shí)我們希望使用某個(gè)對(duì)象或者模塊的功能,但是因?yàn)榉N種原因,我們不能直接訪問(wèn),這時(shí)就可以考慮使用代理,對(duì)應(yīng)的UML圖如下:

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