設(shè)計模式概述
面向?qū)ο笃叽笤O(shè)計原則
設(shè)計原則名稱 | 定 義 | 補充 |
---|---|---|
開閉原則(Open-Closed Principle, OCP) | 軟件實體應(yīng)對擴展開放拢锹,而對修改關(guān)閉 | 如果每次需求變動都去修改原有的代碼,那原有的代碼就存在被修改錯誤的風(fēng)險兽叮,當然這其中存在有意和無意的修改芬骄,都會導(dǎo)致原有正常運行的功能失效的風(fēng)險,這樣很有可能會展開可怕的蝴蝶效應(yīng)鹦聪,使維護工作劇增账阻。我們寫完的代碼,不能因為需求變化就修改泽本。我們可以通過新增代碼的方式來解決變化的需求淘太。開閉原則是設(shè)計模式的第一大原則,它的潛臺詞是:控制需求變動風(fēng)險观挎,縮小維護成本琴儿。 |
單一職責原則(Single Responsibility Principle, SRP) | 一個類只負責一個功能領(lǐng)域中的相應(yīng)職責 | 這也是靈活的前提,如果我們把類拆分成最小的職能單位嘁捷,那組合與復(fù)用就簡單的多了造成,如果一個類做的事情太多,在組合的時候雄嚣,必然會產(chǎn)生不必要的方法出現(xiàn)晒屎,這實際上是一種污染。單一職責的潛臺詞是:拆分到最小單位缓升,解決復(fù)用和組合問題鼓鲁。 |
里氏代換原則(Liskov Substitution Principle, LSP) | 所有引用基類對象的地方能夠透明地使用其子類的對象 | 此原則的含義是子類可以在任何地方替換它的父類。解釋一下港谊,這是多態(tài)的前提骇吭,我們后面很多所謂的靈活,都是不改變聲明類型的情況下歧寺,改變實例化類來完成的需求變更燥狰。當然,繼承的特性看似天然就滿足這個條件斜筐。但這里更注重的是繼承的應(yīng)用問題龙致,我們必須保證我們的子類和父類劃分是精準的。里氏替換原則的潛臺詞是:盡量使用精準的抽象類或者接口顷链。 |
依賴倒轉(zhuǎn)原則(Dependence Inversion Principle, DIP) | 抽象不應(yīng)該依賴于細節(jié)目代,細節(jié)應(yīng)該依賴于抽象 | 依賴倒置原則就是要求調(diào)用者和被調(diào)用者都依賴抽象,這樣兩者沒有直接的關(guān)聯(lián)和接觸,在變動的時候榛了,一方的變動不會影響另一方的變動在讶。其實,依賴倒置和前面的原則是相輔相成的霜大,都強調(diào)了抽象的重要性真朗。依賴倒置的潛臺詞是:面向抽象編程,解耦調(diào)用和被調(diào)用者僧诚。 |
接口隔離原則(Interface Segregation Principle, ISP) | 使用多個專門的接口遮婶,而不使用單一的總接口 | 接口隔離原則可以說是單一職責的必要手段,它的含義是盡量使用職能單一的接口湖笨,而不使用職能復(fù)雜旗扑、全面的接口。很好理解慈省,接口是為了讓子類實現(xiàn)的臀防,如果子類想達到職能單一,那么接口也必須滿足職能單一边败。相反袱衷,如果接口融合了多個不相關(guān)的方法,那它的子類就被迫要實現(xiàn)所有方法笑窜,盡管有些方法是根本用不到的致燥。這就是接口污染。接口隔離原則的潛臺詞是:拆分排截,從接口開始嫌蚤。 |
合成復(fù)用原則(Composite Reuse Principle, CRP) | 盡量使用對象組合购撼,而不是繼承來達到復(fù)用的目的 | 如果只是達到代碼復(fù)用的目的撩幽,盡量使用組合與聚合讥珍,而不是繼承无蜂。這里需要解釋一下,組合聚合只是引用其他的類的方法简软,而不會受引用的類的繼承而改變血統(tǒng)煤杀。繼承的耦合性更大汇陆,比如一個父類后來添加實現(xiàn)一個接口或者去掉一個接口垦垂,那子類可能會遭到毀滅性的編譯錯誤宦搬,但如果只是組合聚合,只是引用類的方法乔外,就不會有這種巨大的風(fēng)險床三,同時也實現(xiàn)了復(fù)用一罩。組合聚合復(fù)用原則的潛臺詞是:我只是用你的方法杨幼,我們不一定是同類。 |
迪米特法則(Law of Demeter, LoD) | 一個軟件實體應(yīng)當盡可能少地與其他實體發(fā)生相互作用 | 迪米特原則要求盡量的封裝,盡量的獨立差购,盡量的使用低級別的訪問修飾符四瘫。這是封裝特性的典型體現(xiàn)。一個類如果暴露太多私用的方法和字段欲逃,會讓調(diào)用者很茫然找蜜。并且會給類造成不必要的判斷代碼。所以稳析,我們使用盡量低的訪問修飾符洗做,讓外界不知道我們的內(nèi)部。這也是面向?qū)ο蟮幕舅悸氛镁印A硗獬现剑厦滋卦瓌t要求類之間的直接聯(lián)系盡量的少,兩個類的訪問陈惰,通過第三個中介類來實現(xiàn)畦徘。迪米特原則的潛臺詞是:不和陌生人說話,有事找中介抬闯。 |
在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計原則時需要注意以下幾點:
a) 高內(nèi)聚井辆、低耦合和單一職能的“沖突”
實際上,這兩者是一回事溶握。內(nèi)聚杯缺,要求一個類把所有相關(guān)的方法放在一起,初看是職能多睡榆,但有個“高”夺谁,就是要求把聯(lián)系非常緊密的功能放在一起,也就是說肉微,從整體看匾鸥,是一個職能的才能放在一起,所以碉纳,兩者是不同的表述而已勿负。
這里很多人理解成復(fù)合類,但復(fù)合類不是高內(nèi)聚劳曹,而是雜亂的放在一起奴愉,是一種設(shè)計失誤而已。
b)多個單一職能接口的靈活性和聲明類型問題
如果一個類實現(xiàn)多個接口铁孵,那么這個類應(yīng)該用哪個接口類型聲明呢锭硼?應(yīng)該是用一個抽象類來繼承多個接口,而實現(xiàn)類來繼承抽象類蜕劝。聲明的時候檀头,類型是抽象類轰异。
c) 迪米特原則和中介類泛濫兩種極端情況
這是另一種設(shè)計的失誤。迪米特原則第一要義:從被依賴者的角度來說暑始,只暴露應(yīng)該暴露的方法或者屬性搭独,即在編寫相關(guān)的類的時候確定方法/屬性的權(quán)限。迪米特原則第二要義:從依賴者的角度來說廊镜,只依賴應(yīng)該依賴的對象牙肝。迪米特原則要求不直接相關(guān)類之間要用中介來通訊,但類多了以后嗤朴,會造成中介類泛濫的情況配椭,這種情況,我們可以考慮中介模式雹姊,用一個總的中介類來實現(xiàn)颂郎。當然,設(shè)計模式都有自己的缺陷容为,迪米特原則也不是十全十美乓序,交互類非常繁多的情況下,要適當?shù)臓奚O(shè)計原則坎背。
d) 繼承和組合聚合復(fù)用原則的“沖突”
繼承也能實現(xiàn)復(fù)用替劈,那這個原則是不是要拋棄繼承了?不是的得滤。
繼承更注重的是“血統(tǒng)”陨献,也就是什么類型的。而組合聚合更注重的是借用“技能”懂更。并且眨业,組合聚合中,兩個類是部分與整體的關(guān)系沮协,組合聚合可以由多個類的技能組成龄捡。這個原則不是告訴我們不用繼承了,都用組合聚合慷暂,而是在“復(fù)用”這個點上聘殖,我們優(yōu)先使用組合聚合。
基于以上設(shè)計原則行瑞,GoF(設(shè)計模式總結(jié)4人組)總結(jié)了軟件開發(fā)領(lǐng)域的23個經(jīng)典設(shè)計模式奸腺。雖然GoF設(shè)計模式只有23個,但是它們各具特色血久,每個模式都為某一個可重復(fù)的設(shè)計問題提供了一套解決方案突照。根據(jù)它們的用途,設(shè)計模式可分為創(chuàng)建型(Creational)氧吐,結(jié)構(gòu)型(Structural)和行為型(Behavioral)三種讹蘑,其中創(chuàng)建型模式主要用于描述如何創(chuàng)建對象末盔,結(jié)構(gòu)型模式主要用于描述如何實現(xiàn)類或?qū)ο蟮慕M合,行為型模式主要用于描述類或?qū)ο笤鯓咏换ヒ约霸鯓臃峙渎氊熛沃贕oF 23種設(shè)計模式中包含5種創(chuàng)建型設(shè)計模式、7種結(jié)構(gòu)型設(shè)計模式和11種行為型設(shè)計模式豁翎。此外角骤,根據(jù)某個模式主要是用于處理類之間的關(guān)系還是對象之間的關(guān)系,設(shè)計模式還可以分為類模式和對象模式心剥。我們經(jīng)常將兩種分類方式結(jié)合使用邦尊,如單例模式是對象創(chuàng)建型模式,模板方法模式是類行為型模式优烧。
值得一提的是蝉揍,有一個設(shè)計模式雖然不屬于GoF 23種設(shè)計模式,但一般在介紹設(shè)計模式時都會對它進行說明畦娄,它就是簡單工廠模式又沾,也許是太“簡單”了,GoF并沒有把它寫到那本經(jīng)典著作中熙卡,不過現(xiàn)在大部分的設(shè)計模式書籍都會對它進行專門的介紹杖刷。
常用設(shè)計模式一覽表
類型 | 模式名稱 | 學(xué)習(xí)難度 | 使用頻率 |
---|---|---|---|
創(chuàng)建型模式(Creational Pattern) | 單例模式(Singleton Pattern) | ★☆☆☆☆ | ★★★★☆ |
創(chuàng)建型模式(Creational Pattern) | 簡單工廠模式(Simple Factory Pattern) | ★★☆☆☆ | ★★★☆☆ |
創(chuàng)建型模式(Creational Pattern) | 工廠方法模式(Factory Method Pattern) | ★★☆☆☆ | ★★★★★ |
創(chuàng)建型模式(Creational Pattern) | 抽象工廠模式(Abstract Factory Pattern) | ★★★★☆ | ★★★★★ |
創(chuàng)建型模式(Creational Pattern) | 原型模式(Prototype Pattern) | ★★★☆☆ | ★★★☆☆ |
創(chuàng)建型模式(Creational Pattern) | 建造者模式(Builder Pattern) | ★★★★☆ | ★★☆☆☆ |
結(jié)構(gòu)型模式(Structural Pattern) | 適配器模式(Adapter Pattern) | ★★☆☆☆ | ★★★★☆ |
結(jié)構(gòu)型模式(Structural Pattern) | 橋接模式(Bridge Pattern) | ★★★☆☆ | ★★★☆☆ |
結(jié)構(gòu)型模式(Structural Pattern) | 組合模式(Composite Pattern) | ★★★☆☆ | ★★★★☆ |
結(jié)構(gòu)型模式(Structural Pattern) | 裝飾模式(Decorator Pattern) | ★★★☆☆ | ★★★☆☆ |
結(jié)構(gòu)型模式(Structural Pattern) | 外觀模式(Fa?ade Pattern) | ★☆☆☆☆ | ★★★★★ |
結(jié)構(gòu)型模式(Structural Pattern) | 享元模式(Flyweight Pattern) | ★★★★☆ | ★☆☆☆☆ |
結(jié)構(gòu)型模式(Structural Pattern) | 代理模式(Proxy Pattern) | ★★★☆☆ | ★★★★☆ |
行為型模式(Behavioral Pattern) | 職責鏈模式(Chain of Responsibility Pattern) | ★★★☆☆ | ★★☆☆☆ |
行為型模式(Behavioral Pattern) | 命令模式(Command Pattern) | ★★★☆☆ | ★★★★☆ |
行為型模式(Behavioral Pattern) | 解釋器模式(Interpreter Pattern) | ★★★★★ | ★☆☆☆☆ |
行為型模式(Behavioral Pattern) | 迭代器模式(Iterator Pattern) | ★★★☆☆ | ★★★★★ |
行為型模式(Behavioral Pattern) | 中介者模式(Mediator Pattern) | ★★★☆☆ | ★★☆☆☆ |
行為型模式(Behavioral Pattern) | 備忘錄模式(Memento Pattern) | ★★☆☆☆ | ★★☆☆☆ |
行為型模式(Behavioral Pattern) | 觀察者模式(Observer Pattern) | ★★★☆☆ | ★★★★★ |
行為型模式(Behavioral Pattern) | 狀態(tài)模式(State Pattern) | ★★★☆☆ | ★★★☆☆ |
行為型模式(Behavioral Pattern) | 策略模式(Strategy Pattern) | ★☆☆☆☆ | ★★★★☆ |
行為型模式(Behavioral Pattern) | 模板方法模式(Template Method Pattern) | ★★☆☆☆ | ★★★☆☆ |
行為型模式(Behavioral Pattern) | 訪問者模式(Visitor Pattern) | ★★★★☆ | ★☆☆☆☆ |
設(shè)計模式詳解
創(chuàng)建型模式(Creational Pattern)
單例模式(Singleton Pattern)
單例模式(Singleton Pattern):確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例驳癌,這個類稱為單例類滑燃,它提供全局訪問的方法。單例模式是一種對象創(chuàng)建型模式颓鲜。
單例模式有三個要點:一是某個類只能有一個實例表窘;二是它必須自行創(chuàng)建這個實例;三是它必須自行向整個系統(tǒng)提供這個實例甜滨。
在單例類的內(nèi)部實現(xiàn)只生成一個實例乐严,同時它提供一個靜態(tài)的getInstance()工廠方法,讓客戶可以訪問它的唯一實例衣摩;為了防止在外部對其實例化麦备,將其構(gòu)造函數(shù)設(shè)計為私有;在單例類內(nèi)部定義了一個Singleton類型的靜態(tài)對象昭娩,作為外部共享的唯一實例凛篙。
單例寫法
餓漢式--在類初始化時生成初始化實例對象。
public SingletonInstance{
private static Instance instance = new Instance();
public static Instance getInstance(){
return instance;
}
}
懶漢式寫法--雙重檢查鎖定和延遲初始化
寫法一:
private static Instance instance;
public static synchronized Instance getInstance(){
if (instance == null){
instance = new Instance();
}
return instance;
}
}
這種做法的問題是很明顯的栏渺,每一次讀取instance都需要同步呛梆,可能會對性能產(chǎn)生較大的影響。
寫法二:
public static Instance getInstance(){
if (instance == null){
synchronized(UnsafeLazyInit.classs){
if (instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
這種方案看似解決了上面兩種方案都存在的問題磕诊,但是也是有問題的填物。
問題根源
instance = new Instance();
這一條語句在實際執(zhí)行中纹腌,可能會被拆分程三條語句,如下:
memory = allocate(); //1
createInstance(memory); //2
instance = memory; //3
根據(jù)重排序規(guī)則滞磺,后兩條語句不存在數(shù)據(jù)依賴升薯,因此是可以進行重排序的。重排序之后击困,就意味著涎劈,instance域在被賦值了之后,指向的對象可能尚未初始化完成阅茶,而instance域是一個靜態(tài)域蛛枚,可以被其他線程讀取到,那么其他線程就可以讀取到尚未初始化完成的instance域脸哀。
基于volatile的解決方案
要解決這個辦法蹦浦,只需要禁止語句2和語句3進行重排序即可,因此可以使用volatile來修改instance就能做到了撞蜂。
private volatile static Instance instance;
因為Volatile語義會禁止編譯器將volatile寫之前的操作重排序到volatile之后盲镶。
寫法三:
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance ; //這里將導(dǎo)致InstanceHolder類被初始化
}
}
單例模式總結(jié)
1.主要優(yōu)點
(1) 單例模式提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例蝌诡,所以它可以嚴格控制客戶怎樣以及何時訪問它寓盗。
(2) 由于在系統(tǒng)內(nèi)存中只存在一個對象进胯,因此可以節(jié)約系統(tǒng)資源摔认,對于一些需要頻繁創(chuàng)建和銷毀的對象單例模式無疑可以提高系統(tǒng)的性能狼荞。
(3) 允許可變數(shù)目的實例∶龉眩基于單例模式我們可以進行擴展代兵,使用與單例控制相似的方法來獲得指定個數(shù)的對象實例,既節(jié)省系統(tǒng)資源爷狈,又解決了單例單例對象共享過多有損性能的問題植影。
2.主要缺點
(1) 由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難涎永。
(2) 單例類的職責過重思币,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色羡微,提供了工廠方法谷饿,同時又充當了產(chǎn)品角色,包含一些業(yè)務(wù)方法妈倔,將產(chǎn)品的創(chuàng)建和產(chǎn)品的本身的功能融合到一起博投。
(3) 現(xiàn)在很多面向?qū)ο笳Z言(如Java、C#)的運行環(huán)境都提供了自動垃圾回收的技術(shù)盯蝴,因此毅哗,如果實例化的共享對象長時間不被利用听怕,系統(tǒng)會認為它是垃圾,會自動銷毀并回收資源虑绵,下次利用時又將重新實例化尿瞭,這將導(dǎo)致共享的單例對象狀態(tài)的丟失。
3.適用場景
(1) 系統(tǒng)只需要一個實例對象翅睛,如系統(tǒng)要求提供一個唯一的序列號生成器或資源管理器声搁,或者需要考慮資源消耗太大而只允許創(chuàng)建一個對象。
(2) 客戶調(diào)用類的單個實例只允許使用一個公共訪問點宏所,除了該公共訪問點酥艳,不能通過其他途徑訪問該實例摊溶。
簡單工廠模式
簡單工廠模式(Simple Factory Pattern):定義一個工廠類爬骤,它可以根據(jù)參數(shù)的不同返回不同類的實例,被創(chuàng)建的實例通常都具有共同的父類莫换。因為在簡單工廠模式中用于創(chuàng)建實例的方法是靜態(tài)(static)方法霞玄,因此簡單工廠模式又被稱為靜態(tài)工廠方法(Static Factory Method)模式,它屬于類創(chuàng)建型模式拉岁。UML圖如下:
在簡單工廠模式結(jié)構(gòu)圖中包含如下幾個角色:
● Factory(工廠角色):工廠角色即工廠類坷剧,它是簡單工廠模式的核心,負責實現(xiàn)創(chuàng)建所有產(chǎn)品實例的內(nèi)部邏輯喊暖;工廠類可以被外界直接調(diào)用惫企,創(chuàng)建所需的產(chǎn)品對象;在工廠類中提供了靜態(tài)的工廠方法factoryMethod()陵叽,它的返回類型為抽象產(chǎn)品類型Product狞尔。
● Product(抽象產(chǎn)品角色):它是工廠類所創(chuàng)建的所有對象的父類,封裝了各種產(chǎn)品對象的公有方法巩掺,它的引入將提高系統(tǒng)的靈活性偏序,使得在工廠類中只需定義一個通用的工廠方法,因為所有創(chuàng)建的具體產(chǎn)品對象都是其子類對象胖替。
● ConcreteProduct(具體產(chǎn)品角色):它是簡單工廠模式的創(chuàng)建目標研儒,所有被創(chuàng)建的對象都充當這個角色的某個具體類的實例。每一個具體產(chǎn)品角色都繼承了抽象產(chǎn)品角色独令,需要實現(xiàn)在抽象產(chǎn)品中聲明的抽象方法端朵。
在簡單工廠模式中,客戶端通過工廠類來創(chuàng)建一個產(chǎn)品類的實例燃箭,而無須直接使用new關(guān)鍵字來創(chuàng)建對象逸月,它是工廠模式家族中最簡單的一員。
在使用簡單工廠模式時遍膜,首先需要對產(chǎn)品類進行重構(gòu)碗硬,不能設(shè)計一個包羅萬象的產(chǎn)品類瓤湘,而需根據(jù)實際情況設(shè)計一個產(chǎn)品層次結(jié)構(gòu),將所有產(chǎn)品類公共的代碼移至抽象產(chǎn)品類恩尾,并在抽象產(chǎn)品類中聲明一些抽象方法弛说,以供不同的具體產(chǎn)品類來實現(xiàn)。
簡單工廠模式總結(jié)
簡單工廠模式提供了專門的工廠類用于創(chuàng)建對象翰意,將對象的創(chuàng)建和對象的使用分離開木人,它作為一種最簡單的工廠模式在軟件開發(fā)中得到了較為廣泛的應(yīng)用。
- 主要優(yōu)點
(1) 工廠類包含必要的判斷邏輯冀偶,可以決定在什么時候創(chuàng)建哪一個產(chǎn)品類的實例醒第,客戶端可以免除直接創(chuàng)建產(chǎn)品對象的職責,而僅僅“消費”產(chǎn)品进鸠,簡單工廠模式實現(xiàn)了對象創(chuàng)建和使用的分離稠曼。
(2) 客戶端無須知道所創(chuàng)建的具體產(chǎn)品類的類名,只需要知道具體產(chǎn)品類所對應(yīng)的參數(shù)即可客年,對于一些復(fù)雜的類名霞幅,通過簡單工廠模式可以在一定程度減少使用者的記憶量。
(3) 通過引入配置文件量瓜,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產(chǎn)品類司恳,在一定程度上提高了系統(tǒng)的靈活性。 - 主要缺點
(1) 由于工廠類集中了所有產(chǎn)品的創(chuàng)建邏輯绍傲,職責過重扔傅,一旦不能正常工作,整個系統(tǒng)都要受到影響烫饼。
(2) 使用簡單工廠模式勢必會增加系統(tǒng)中類的個數(shù)(引入了新的工廠類)猎塞,增加了系統(tǒng)的復(fù)雜度和理解難度。
(3) 系統(tǒng)擴展困難枫弟,一旦添加新產(chǎn)品就不得不修改工廠邏輯邢享,在產(chǎn)品類型較多時,有可能造成工廠邏輯過于復(fù)雜淡诗,不利于系統(tǒng)的擴展和維護骇塘。
(4) 簡單工廠模式由于使用了靜態(tài)工廠方法,造成工廠角色無法形成基于繼承的等級結(jié)構(gòu)韩容。 - 適用場景
(1) 工廠類負責創(chuàng)建的對象比較少款违,由于創(chuàng)建的對象較少,不會造成工廠方法中的業(yè)務(wù)邏輯太過復(fù)雜群凶。
(2) 客戶端只知道傳入工廠類的參數(shù)插爹,對于如何創(chuàng)建對象并不關(guān)心。
工廠模式
簡單工廠模式雖然簡單,但存在一個很嚴重的問題赠尾。當系統(tǒng)中需要引入新產(chǎn)品時力穗,由于靜態(tài)工廠方法通過所傳入?yún)?shù)的不同來創(chuàng)建不同的產(chǎn)品,這必定要修改工廠類的源代碼气嫁,將違背“開閉原則”当窗,如何實現(xiàn)增加新產(chǎn)品而不影響已有代碼?工廠方法模式應(yīng)運而生寸宵。
在簡單工廠模式中只提供一個工廠類崖面,該工廠類處于對產(chǎn)品類進行實例化的中心位置,它需要知道每一個產(chǎn)品對象的創(chuàng)建細節(jié)梯影,并決定何時實例化哪一個產(chǎn)品類巫员。簡單工廠模式最大的缺點是當有新產(chǎn)品要加入到系統(tǒng)中時,必須修改工廠類甲棍,需要在其中加入必要的業(yè)務(wù)邏輯简识,這違背了“開閉原則”。此外救军,在簡單工廠模式中财异,所有的產(chǎn)品都由同一個工廠創(chuàng)建倘零,工廠類職責較重唱遭,業(yè)務(wù)邏輯較為復(fù)雜,具體產(chǎn)品與工廠類之間的耦合度高呈驶,嚴重影響了系統(tǒng)的靈活性和擴展性拷泽,而工廠方法模式則可以很好地解決這一問題。
在工廠方法模式中袖瞻,不再提供一個統(tǒng)一的工廠類來創(chuàng)建所有的產(chǎn)品對象司致,而是針對不同的產(chǎn)品提供不同的工廠,系統(tǒng)提供一個與產(chǎn)品等級結(jié)構(gòu)對應(yīng)的工廠等級結(jié)構(gòu)聋迎。
在工廠方法模式結(jié)構(gòu)圖中包含如下幾個角色:
● Product(抽象產(chǎn)品):它是定義產(chǎn)品的接口脂矫,是工廠方法模式所創(chuàng)建對象的超類型,也就是產(chǎn)品對象的公共父類霉晕。
● ConcreteProduct(具體產(chǎn)品):它實現(xiàn)了抽象產(chǎn)品接口庭再,某種類型的具體產(chǎn)品由專門的具體工廠創(chuàng)建,具體工廠和具體產(chǎn)品之間一一對應(yīng)牺堰。
● Factory(抽象工廠):在抽象工廠類中拄轻,聲明了工廠方法(Factory Method),用于返回一個產(chǎn)品伟葫。抽象工廠是工廠方法模式的核心恨搓,所有創(chuàng)建對象的工廠類都必須實現(xiàn)該接口。
● ConcreteFactory(具體工廠):它是抽象工廠類的子類,實現(xiàn)了抽象工廠中定義的工廠方法斧抱,并可由客戶端調(diào)用常拓,返回一個具體產(chǎn)品類的實例。
與簡單工廠模式相比辉浦,工廠方法模式最重要的區(qū)別是引入了抽象工廠角色墩邀,抽象工廠可以是接口,也可以是抽象類或者具體類盏浙。下圖是以日志記錄器為例的工廠模式眉睹。
工廠方法模式總結(jié)
工廠方法模式是簡單工廠模式的延伸,它繼承了簡單工廠模式的優(yōu)點废膘,同時還彌補了簡單工廠模式的不足竹海。工廠方法模式是使用頻率最高的設(shè)計模式之一,是很多開源框架和API類庫的核心模式丐黄。
- 主要優(yōu)點
(1) 在工廠方法模式中斋配,工廠方法用來創(chuàng)建客戶所需要的產(chǎn)品,同時還向客戶隱藏了哪種具體產(chǎn)品類將被實例化這一細節(jié)灌闺,用戶只需要關(guān)心所需產(chǎn)品對應(yīng)的工廠艰争,無須關(guān)心創(chuàng)建細節(jié),甚至無須知道具體產(chǎn)品類的類名。
(2) 基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計是工廠方法模式的關(guān)鍵球匕。它能夠讓工廠可以自主確定創(chuàng)建何種產(chǎn)品對象释簿,而如何創(chuàng)建這個對象的細節(jié)則完全封裝在具體工廠內(nèi)部。工廠方法模式之所以又被稱為多態(tài)工廠模式逾柿,就正是因為所有的具體工廠類都具有同一抽象父類。
(3) 使用工廠方法模式的另一個優(yōu)點是在系統(tǒng)中加入新產(chǎn)品時宅此,無須修改抽象工廠和抽象產(chǎn)品提供的接口机错,無須修改客戶端,也無須修改其他的具體工廠和具體產(chǎn)品父腕,而只要添加一個具體工廠和具體產(chǎn)品就可以了弱匪,這樣,系統(tǒng)的可擴展性也就變得非常好璧亮,完全符合“開閉原則”萧诫。 - 主要缺點
(1) 在添加新產(chǎn)品時,需要編寫新的具體產(chǎn)品類杜顺,而且還要提供與之對應(yīng)的具體工廠類财搁,系統(tǒng)中類的個數(shù)將成對增加,在一定程度上增加了系統(tǒng)的復(fù)雜度躬络,有更多的類需要編譯和運行尖奔,會給系統(tǒng)帶來一些額外的開銷。
(2) 由于考慮到系統(tǒng)的可擴展性,需要引入抽象層提茁,在客戶端代碼中均使用抽象層進行定義淹禾,增加了系統(tǒng)的抽象性和理解難度,且在實現(xiàn)時可能需要用到DOM茴扁、反射等技術(shù)铃岔,增加了系統(tǒng)的實現(xiàn)難度。 - 適用場景
(1) 客戶端不知道它所需要的對象的類峭火。在工廠方法模式中毁习,客戶端不需要知道具體產(chǎn)品類的類名,只需要知道所對應(yīng)的工廠即可卖丸,具體的產(chǎn)品對象由具體工廠類創(chuàng)建纺且,可將具體工廠類的類名存儲在配置文件或數(shù)據(jù)庫中。
(2) 抽象工廠類通過其子類來指定創(chuàng)建哪個對象稍浆。在工廠方法模式中载碌,對于抽象工廠類只需要提供一個創(chuàng)建產(chǎn)品的接口,而由其子類來確定具體要創(chuàng)建的對象衅枫,利用面向?qū)ο蟮亩鄳B(tài)性和里氏代換原則嫁艇,在程序運行時,子類對象將覆蓋父類對象弦撩,從而使得系統(tǒng)更容易擴展步咪。
抽象工廠
由于工廠方法模式中的每個工廠只生產(chǎn)一類產(chǎn)品,可能會導(dǎo)致系統(tǒng)中存在大量的工廠類孤钦,勢必會增加系統(tǒng)的開銷歧斟。此時纯丸,我們可以考慮將一些相關(guān)的產(chǎn)品組成一個“產(chǎn)品族”偏形,由同一個工廠來統(tǒng)一生產(chǎn),這就是抽象工廠模式的基本思想觉鼻。
在工廠方法模式中具體工廠負責生產(chǎn)具體的產(chǎn)品俊扭,每一個具體工廠對應(yīng)一種具體產(chǎn)品,工廠方法具有唯一性坠陈,一般情況下萨惑,一個具體工廠中只有一個或者一組重載的工廠方法。但是有時候我們希望一個工廠可以提供多個產(chǎn)品對象仇矾,而不是單一的產(chǎn)品對象庸蔼,如一個電器工廠,它可以生產(chǎn)電視機贮匕、電冰箱姐仅、空調(diào)等多種電器,而不是只生產(chǎn)某一種電器。為了更好地理解抽象工廠模式掏膏,我們先引入兩個概念:
(1) 產(chǎn)品等級結(jié)構(gòu):產(chǎn)品等級結(jié)構(gòu)即產(chǎn)品的繼承結(jié)構(gòu)劳翰,如一個抽象類是電視機,其子類有海爾電視機馒疹、海信電視機佳簸、TCL電視機,則抽象電視機與具體品牌的電視機之間構(gòu)成了一個產(chǎn)品等級結(jié)構(gòu)颖变,抽象電視機是父類生均,而具體品牌的電視機是其子類。
(2) 產(chǎn)品族:在抽象工廠模式中腥刹,產(chǎn)品族是指由同一個工廠生產(chǎn)的疯特,位于不同產(chǎn)品等級結(jié)構(gòu)中的一組產(chǎn)品,如海爾電器工廠生產(chǎn)的海爾電視機肛走、海爾電冰箱漓雅,海爾電視機位于電視機產(chǎn)品等級結(jié)構(gòu)中,海爾電冰箱位于電冰箱產(chǎn)品等級結(jié)構(gòu)中朽色,海爾電視機邻吞、海爾電冰箱構(gòu)成了一個產(chǎn)品族。
當系統(tǒng)所提供的工廠生產(chǎn)的具體產(chǎn)品并不是一個簡單的對象葫男,而是多個位于不同產(chǎn)品等級結(jié)構(gòu)抱冷、屬于不同類型的具體產(chǎn)品時就可以使用抽象工廠模式。抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形式梢褐。抽象工廠模式與工廠方法模式最大的區(qū)別在于旺遮,工廠方法模式針對的是一個產(chǎn)品等級結(jié)構(gòu),而抽象工廠模式需要面對多個產(chǎn)品等級結(jié)構(gòu)盈咳,一個工廠等級結(jié)構(gòu)可以負責多個不同產(chǎn)品等級結(jié)構(gòu)中的產(chǎn)品對象的創(chuàng)建耿眉。當一個工廠等級結(jié)構(gòu)可以創(chuàng)建出分屬于不同產(chǎn)品等級結(jié)構(gòu)的一個產(chǎn)品族中的所有對象時,抽象工廠模式比工廠方法模式更為簡單鱼响、更有效率鸣剪。
抽象工廠模式為創(chuàng)建一組對象提供了一種解決方案。與工廠方法模式相比丈积,抽象工廠模式中的具體工廠不只是創(chuàng)建一種產(chǎn)品筐骇,它負責創(chuàng)建一族產(chǎn)品。
AbstractFactory(抽象工廠)的結(jié)構(gòu)圖中的角色和工廠模式一樣江滨,唯一的區(qū)別是AbstractFactory模式的抽象工廠角色聲明了一組(非工廠模式的一個)用于創(chuàng)建一族產(chǎn)品的方法铛纬,每一個方法對應(yīng)一種產(chǎn)品。如下所示:
abstract class AbstractFactory {
public abstract AbstractProductA createProductA(); //工廠方法一
public abstract AbstractProductB createProductB(); //工廠方法二
……
在抽象工廠模式中唬滑,增加新的產(chǎn)品族很方便告唆,但是增加新的產(chǎn)品等級結(jié)構(gòu)很麻煩莫秆,抽象工廠模式的這種性質(zhì)稱為“開閉原則”的傾斜性』谙辏“開閉原則”要求系統(tǒng)對擴展開放镊屎,對修改封閉,通過擴展達到增強其功能的目的茄螃,對于涉及到多個產(chǎn)品族與多個產(chǎn)品等級結(jié)構(gòu)的系統(tǒng)缝驳,其功能增強包括兩方面:
(1) 增加產(chǎn)品族:對于增加新的產(chǎn)品族,抽象工廠模式很好地支持了“開閉原則”归苍,只需要增加具體產(chǎn)品并對應(yīng)增加一個新的具體工廠用狱,對已有代碼無須做任何修改。
(2) 增加新的產(chǎn)品等級結(jié)構(gòu):對于增加新的產(chǎn)品等級結(jié)構(gòu)拼弃,需要修改所有的工廠角色夏伊,包括抽象工廠類,在所有的工廠類中都需要增加生產(chǎn)新產(chǎn)品的方法吻氧,違背了“開閉原則”溺忧。
正因為抽象工廠模式存在“開閉原則”的傾斜性,它以一種傾斜的方式來滿足“開閉原則”盯孙,為增加新產(chǎn)品族提供方便鲁森,但不能為增加新產(chǎn)品結(jié)構(gòu)提供這樣的方便,因此要求設(shè)計人員在設(shè)計之初就能夠全面考慮振惰,不會在設(shè)計完成之后向系統(tǒng)中增加新的產(chǎn)品等級結(jié)構(gòu)歌溉,也不會刪除已有的產(chǎn)品等級結(jié)構(gòu),否則將會導(dǎo)致系統(tǒng)出現(xiàn)較大的修改骑晶,為后續(xù)維護工作帶來諸多麻煩痛垛。
抽象工廠模式總結(jié)
抽象工廠模式是工廠方法模式的進一步延伸,由于它提供了功能更為強大的工廠類并且具備較好的可擴展性桶蛔,在軟件開發(fā)中得以廣泛應(yīng)用匙头,尤其是在一些框架和API類庫的設(shè)計中,例如在Java語言的AWT(抽象窗口工具包)中就使用了抽象工廠模式羽圃,它使用抽象工廠模式來實現(xiàn)在不同的操作系統(tǒng)中應(yīng)用程序呈現(xiàn)與所在操作系統(tǒng)一致的外觀界面乾胶。抽象工廠模式也是在軟件開發(fā)中最常用的設(shè)計模式之一。
- 主要優(yōu)點
(1) 抽象工廠模式隔離了具體類的生成朽寞,使得客戶并不需要知道什么被創(chuàng)建。由于這種隔離斩郎,更換一個具體工廠就變得相對容易脑融,所有的具體工廠都實現(xiàn)了抽象工廠中定義的那些公共接口,因此只需改變具體工廠的實例缩宜,就可以在某種程度上改變整個軟件系統(tǒng)的行為肘迎。
(2) 當一個產(chǎn)品族中的多個對象被設(shè)計成一起工作時甥温,它能夠保證客戶端始終只使用同一個產(chǎn)品族中的對象。
(3) 增加新的產(chǎn)品族很方便妓布,無須修改已有系統(tǒng)姻蚓,符合“開閉原則”。 - 主要缺點
增加新的產(chǎn)品等級結(jié)構(gòu)麻煩匣沼,需要對原有系統(tǒng)進行較大的修改狰挡,甚至需要修改抽象層代碼,這顯然會帶來較大的不便释涛,違背了“開閉原則”加叁。 - 適用場景
(1) 一個系統(tǒng)不應(yīng)當依賴于產(chǎn)品類實例如何被創(chuàng)建、組合和表達的細節(jié)唇撬,這對于所有類型的工廠模式都是很重要的它匕,用戶無須關(guān)心對象的創(chuàng)建過程,將對象的創(chuàng)建和使用解耦窖认。
(2) 系統(tǒng)中有多于一個的產(chǎn)品族豫柬,而每次只使用其中某一產(chǎn)品族∑私可以通過配置文件等方式來使得用戶可以動態(tài)改變產(chǎn)品族轮傍,也可以很方便地增加新的產(chǎn)品族。
(3) 屬于同一個產(chǎn)品族的產(chǎn)品將在一起使用首装,這一約束必須在系統(tǒng)的設(shè)計中體現(xiàn)出來创夜。同一個產(chǎn)品族中的產(chǎn)品可以是沒有任何關(guān)系的對象,但是它們都具有一些共同的約束仙逻,如同一操作系統(tǒng)下的按鈕和文本框驰吓,按鈕與文本框之間沒有直接關(guān)系汽馋,但它們都是屬于某一操作系統(tǒng)的归薛,此時具有一個共同的約束條件:操作系統(tǒng)的類型绊含。
(4) 產(chǎn)品等級結(jié)構(gòu)穩(wěn)定典蝌,設(shè)計完成之后政模,不會向系統(tǒng)中增加新的產(chǎn)品等級結(jié)構(gòu)或者刪除已有的產(chǎn)品等級結(jié)構(gòu)蜗字。
原型模式
原型模式(Prototype Pattern):使用原型實例指定創(chuàng)建對象的種類形庭,并且通過拷貝這些原型創(chuàng)建新的對象延旧。原型模式是一種對象創(chuàng)建型模式萌踱。
由于在軟件系統(tǒng)中我們經(jīng)常會遇到需要創(chuàng)建多個相同或者相似對象的情況葵礼,因此原型模式在真實開發(fā)中的使用頻率還是非常高的。原型模式是一種“另類”的創(chuàng)建型模式并鸵,創(chuàng)建克隆對象的工廠就是原型類自身鸳粉,工廠方法由克隆方法來實現(xiàn)。
需要注意的是通過克隆方法所創(chuàng)建的對象是全新的對象园担,它們在內(nèi)存中擁有新的地址届谈,通常對克隆所產(chǎn)生的對象進行修改對原型對象不會造成任何影響,每一個克隆對象都是相互獨立的艰山。通過不同的方式修改可以得到一系列相似但不完全相同的對象湖雹。原型模式的結(jié)構(gòu)如下圖所示:
在原型模式結(jié)構(gòu)圖中包含如下幾個角色:
●Prototype(抽象原型類):它是聲明克隆方法的接口,是所有具體原型類的公共父類曙搬,可以是抽象類也可以是接口摔吏,甚至還可以是具體實現(xiàn)類。
● ConcretePrototype(具體原型類):它實現(xiàn)在抽象原型類中聲明的克隆方法织鲸,在克隆方法中返回自己的一個克隆對象舔腾。
● Client(客戶類):讓一個原型對象克隆自身從而創(chuàng)建一個新的對象,在客戶類中只需要直接實例化或通過工廠方法等方式創(chuàng)建一個原型對象搂擦,再通過調(diào)用該對象的克隆方法即可得到多個相同的對象稳诚。由于客戶類針對抽象原型類Prototype編程,因此用戶可以根據(jù)需要選擇具體原型類瀑踢,系統(tǒng)具有較好的可擴展性扳还,增加或更換具體原型類都很方便。
原型模式的核心在于如何實現(xiàn)克隆方法橱夭,下面將介紹兩種在Java語言中常用的克隆實現(xiàn)方法:
方式一:創(chuàng)建對象+賦值
public Prototype clone() {
Prototype prototype = new ConcretePrototype(); //創(chuàng)建新對象
prototype.setAttr(this.attr);
return prototype;
}
方式二:Java語言提供的clone()方法
所有的Java類都繼承自java.lang.Object氨距。事實上,Object類提供一個clone()方法棘劣,可以將一個Java對象復(fù)制一份俏让。因此在Java中可以直接使用Object提供的clone()方法來實現(xiàn)對象的克隆,Java語言中的原型模式實現(xiàn)很簡單茬暇。
需要注意的是能夠?qū)崿F(xiàn)克隆的Java類必須實現(xiàn)一個標識接口Cloneable首昔,表示這個Java類支持被復(fù)制。如果一個類沒有實現(xiàn)這個接口但是調(diào)用了clone()方法糙俗,Java編譯器將拋出一個CloneNotSupportedException異常勒奇。
public Prototype clone(){
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not support cloneable");
}
return (Prototype )object;
}
淺克隆(ShallowClone)VS深克隆(DeepClone)。
在Java語言中巧骚,數(shù)據(jù)類型分為值類型(基本數(shù)據(jù)類型)和引用類型赊颠,值類型包括int、double劈彪、byte竣蹦、boolean、char等簡單數(shù)據(jù)類型粉臊,引用類型包括類草添、接口、數(shù)組等復(fù)雜類型扼仲。淺克隆和深克隆的主要區(qū)別在于是否支持引用類型的成員變量的復(fù)制远寸。
在淺克隆中,如果原型對象的成員變量是值類型屠凶,將復(fù)制一份給克隆對象驰后;如果原型對象的成員變量是引用類型,則將引用對象的地址復(fù)制一份給克隆對象矗愧,也就是說原型對象和克隆對象的成員變量指向相同的內(nèi)存地址灶芝。
在深克隆中,無論原型對象的成員變量是值類型還是引用類型唉韭,都將復(fù)制一份給克隆對象夜涕,深克隆將原型對象的所有引用對象也復(fù)制一份給克隆對象。簡單來說属愤,在深克隆中女器,除了對象本身被復(fù)制外,對象所包含的所有成員變量也將復(fù)制住诸。在Java語言中驾胆,如果需要實現(xiàn)深克隆,可以通過序列化(Serialization)等方式來實現(xiàn)贱呐。序列化就是將對象寫到流的過程丧诺,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在于內(nèi)存中奄薇。通過序列化實現(xiàn)的拷貝不僅可以復(fù)制對象本身驳阎,而且可以復(fù)制其引用的成員對象,因此通過序列化將對象寫到一個流中馁蒂,再從流里將其讀出來呵晚,可以實現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對象其類必須實現(xiàn)Serializable接口远搪,否則無法實現(xiàn)序列化操作劣纲。
//使用序列化技術(shù)實現(xiàn)深克隆
public WeeklyLog deepClone() throws IOException, ClassNotFoundException, OptionalDataException{
//將對象寫入流中
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(this);
//將對象從流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return (WeeklyLog)ois.readObject();
}
原型模式總結(jié)
原型模式作為一種快速創(chuàng)建大量相同或相似對象的方式,在軟件開發(fā)中應(yīng)用較為廣泛谁鳍,很多軟件提供的復(fù)制(Ctrl + C)和粘貼(Ctrl + V)操作就是原型模式的典型應(yīng)用癞季,下面對該模式的使用效果和適用情況進行簡單的總結(jié)。
1.主要優(yōu)點
(1) 當創(chuàng)建新的對象實例較為復(fù)雜時倘潜,使用原型模式可以簡化對象的創(chuàng)建過程绷柒,通過復(fù)制一個已有實例可以提高新實例的創(chuàng)建效率。
(2) 擴展性較好涮因,由于在原型模式中提供了抽象原型類废睦,在客戶端可以針對抽象原型類進行編程,而將具體原型類寫在配置文件中养泡,增加或減少產(chǎn)品類對原有系統(tǒng)都沒有任何影響嗜湃。
(3) 原型模式提供了簡化的創(chuàng)建結(jié)構(gòu)奈应,工廠方法模式常常需要有一個與產(chǎn)品類等級結(jié)構(gòu)相同的工廠等級結(jié)構(gòu),而原型模式就不需要這樣购披,原型模式中產(chǎn)品的復(fù)制是通過封裝在原型類中的克隆方法實現(xiàn)的杖挣,無須專門的工廠類來創(chuàng)建產(chǎn)品。
(4) 可以使用深克隆的方式保存對象的狀態(tài)刚陡,使用原型模式將對象復(fù)制一份并將其狀態(tài)保存起來惩妇,以便在需要的時候使用(如恢復(fù)到某一歷史狀態(tài)),可輔助實現(xiàn)撤銷操作筐乳。
2.主要缺點
(1) 需要為每一個類配備一個克隆方法歌殃,而且該克隆方法位于一個類的內(nèi)部,當對已有的類進行改造時蝙云,需要修改源代碼氓皱,違背了“開閉原則”。
(2) 在實現(xiàn)深克隆時需要編寫較為復(fù)雜的代碼贮懈,而且當對象之間存在多重的嵌套引用時匀泊,為了實現(xiàn)深克隆,每一層對象對應(yīng)的類都必須支持深克隆朵你,實現(xiàn)起來可能會比較麻煩各聘。
3.適用場景
(1) 創(chuàng)建新對象成本較大(如初始化需要占用較長的時間,占用太多的CPU資源或網(wǎng)絡(luò)資源)抡医,新的對象可以通過原型模式對已有對象進行復(fù)制來獲得躲因,如果是相似對象,則可以對其成員變量稍作修改忌傻。
(2) 如果系統(tǒng)要保存對象的狀態(tài)大脉,而對象的狀態(tài)變化很小,或者對象本身占用內(nèi)存較少時水孩,可以使用原型模式配合備忘錄模式來實現(xiàn)镰矿。
(3) 需要避免使用分層次的工廠類來創(chuàng)建分層次的對象,并且類的實例對象只有一個或很少的幾個組合狀態(tài)俘种,通過復(fù)制原型對象得到新實例可能比使用構(gòu)造函數(shù)創(chuàng)建一個新實例更加方便秤标。
建造者模式
建造者模式(Builder Pattern):將一個復(fù)雜對象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示宙刘。建造者模式是一種對象創(chuàng)建型模式苍姜。
建造者模式一步一步創(chuàng)建一個復(fù)雜的對象,它允許用戶只通過指定復(fù)雜對象的類型和內(nèi)容就可以構(gòu)建它們悬包,用戶不需要知道內(nèi)部的具體構(gòu)建細節(jié)衙猪。建造者模式結(jié)構(gòu)如圖下圖所示:
在建造者模式結(jié)構(gòu)圖中包含如下幾個角色:
● Builder(抽象建造者):它為創(chuàng)建一個產(chǎn)品Product對象的各個部件指定抽象接口,在該接口中一般聲明兩類方法,一類方法是buildPartX()垫释,它們用于創(chuàng)建復(fù)雜對象的各個部件丝格;另一類方法是getResult(),它們用于返回復(fù)雜對象Product饶号。Builder既可以是抽象類铁追,也可以是接口季蚂。
●ConcreteBuilder(具體建造者):它實現(xiàn)了Builder接口茫船,實現(xiàn)各個部件的具體構(gòu)造和裝配方法,定義并明確它所創(chuàng)建的復(fù)雜對象扭屁,也可以提供一個方法返回創(chuàng)建好的復(fù)雜產(chǎn)品對象算谈。
●Product(產(chǎn)品角色):它是被構(gòu)建的復(fù)雜對象,包含多個組成部件料滥,具體建造者創(chuàng)建該產(chǎn)品的內(nèi)部表示并定義它的裝配過程然眼。
● Director(指揮者):指揮者又稱為導(dǎo)演類,它負責安排復(fù)雜對象的建造次序葵腹,指揮者與抽象建造者之間存在關(guān)聯(lián)關(guān)系高每,可以在其construct()建造方法中調(diào)用建造者對象的部件構(gòu)造與裝配方法(調(diào)用buildPartX),完成復(fù)雜對象的建造践宴【洌客戶端一般只需要與指揮者進行交互,在客戶端確定具體建造者的類型阻肩,并實例化具體建造者對象(也可以通過配置文件和反射機制)带欢,然后通過指揮者類的構(gòu)造函數(shù)或者Setter方法將該對象傳入指揮者類中。
在建造者模式的定義中提到了復(fù)雜對象烤惊,那么什么是復(fù)雜對象乔煞?簡單來說,復(fù)雜對象是指那些包含多個成員屬性的對象柒室,這些成員屬性也稱為部件或零件渡贾,如汽車包括方向盤、發(fā)動機雄右、輪胎等部件空骚,電子郵件包括發(fā)件人千元、收件人午笛、主題、內(nèi)容位岔、附件等部件
建造者模式與抽象工廠模式有點相似防楷,但是建造者模式返回一個完整的復(fù)雜產(chǎn)品牺丙,而抽象工廠模式返回一系列相關(guān)的產(chǎn)品;在抽象工廠模式中,客戶端通過選擇具體工廠來生成所需對象冲簿,而在建造者模式中粟判,客戶端通過指定具體建造者類型并指導(dǎo)Director類如何去生成對象,側(cè)重于一步步構(gòu)造一個復(fù)雜對象峦剔,然后將結(jié)果返回档礁。如果將抽象工廠模式看成一個汽車配件生產(chǎn)廠,生成不同類型的汽車配件吝沫,那么建造者模式就是一個汽車組裝廠呻澜,通過對配件進行組裝返回一輛完整的汽車。
在有些情況下惨险,為了簡化系統(tǒng)結(jié)構(gòu)羹幸,可以將Director和抽象建造者Builder進行合并,在Builder中提供逐步構(gòu)建復(fù)雜產(chǎn)品對象的construct()方法辫愉。由于Builder類通常為抽象類栅受,因此可以將construct()方法定義為靜態(tài)(static)方法。
建造者模式總結(jié)
建造者模式的核心在于如何一步步構(gòu)建一個包含多個組成部件的完整對象恭朗,使用相同的構(gòu)建過程構(gòu)建不同的產(chǎn)品屏镊,在軟件開發(fā)中,如果我們需要創(chuàng)建復(fù)雜對象并希望系統(tǒng)具備很好的靈活性和可擴展性可以考慮使用建造者模式痰腮。
1.主要優(yōu)點
(1) 在建造者模式中而芥,客戶端不必知道產(chǎn)品內(nèi)部組成的細節(jié),將產(chǎn)品本身與產(chǎn)品的創(chuàng)建過程解耦诽嘉,使得相同的創(chuàng)建過程可以創(chuàng)建不同的產(chǎn)品對象蔚出。
(2) 每一個具體建造者都相對獨立,而與其他的具體建造者無關(guān)虫腋,因此可以很方便地替換具體建造者或增加新的具體建造者骄酗,用戶使用不同的具體建造者即可得到不同的產(chǎn)品對象。由于指揮者類針對抽象建造者編程悦冀,增加新的具體建造者無須修改原有類庫的代碼趋翻,系統(tǒng)擴展方便,符合“開閉原則”
(3) 可以更加精細地控制產(chǎn)品的創(chuàng)建過程盒蟆。將復(fù)雜產(chǎn)品的創(chuàng)建步驟分解在不同的方法中踏烙,使得創(chuàng)建過程更加清晰,也更方便使用程序來控制創(chuàng)建過程历等。
2.主要缺點
(1) 建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點讨惩,其組成部分相似,如果產(chǎn)品之間的差異性很大寒屯,例如很多組成部分都不相同荐捻,不適合使用建造者模式黍少,因此其使用范圍受到一定的限制。
(2) 如果產(chǎn)品的內(nèi)部變化復(fù)雜处面,可能會導(dǎo)致需要定義很多具體建造者類來實現(xiàn)這種變化厂置,導(dǎo)致系統(tǒng)變得很龐大,增加系統(tǒng)的理解難度和運行成本魂角。
3.適用場景
(1) 需要生成的產(chǎn)品對象有復(fù)雜的內(nèi)部結(jié)構(gòu)昵济,這些產(chǎn)品對象通常包含多個成員屬性。
(2) 需要生成的產(chǎn)品對象的屬性相互依賴野揪,需要指定其生成順序访忿。
(3) 對象的創(chuàng)建過程獨立于創(chuàng)建該對象的類。在建造者模式中通過引入了指揮者類囱挑,將創(chuàng)建過程封裝在指揮者類中醉顽,而不在建造者類和客戶類中。
(4) 隔離復(fù)雜對象的創(chuàng)建和使用平挑,并使得相同的創(chuàng)建過程可以創(chuàng)建不同的產(chǎn)品。
結(jié)構(gòu)型模式
適配器模式
適配器模式中引入了一個被稱為適配器(Adapter)的包裝類系草,而它所包裝的對象稱為適配者(Adaptee)通熄,即被適配的類。適配器的實現(xiàn)就是把客戶類的請求轉(zhuǎn)化為對適配者的相應(yīng)接口的調(diào)用找都。也就是說:當客戶類調(diào)用適配器的方法時唇辨,在適配器類的內(nèi)部將調(diào)用適配者類的方法,而這個過程對客戶類是透明的能耻,客戶類并不直接訪問適配者類赏枚。因此,適配器讓那些由于接口不兼容而不能交互的類可以一起工作晓猛。
適配器模式(Adapter Pattern):將一個接口轉(zhuǎn)換成客戶希望的另一個接口饿幅,使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)戒职。適配器模式既可以作為類結(jié)構(gòu)型模式栗恩,也可以作為對象結(jié)構(gòu)型模式。
根據(jù)適配器類與適配者類的關(guān)系不同洪燥,適配器模式可分為對象適配器和類適配器兩種磕秤,在對象適配器模式中,適配器與適配者之間是關(guān)聯(lián)關(guān)系捧韵;在類適配器模式中市咆,適配器與適配者之間是繼承(或?qū)崿F(xiàn))關(guān)系。在實際開發(fā)中再来,對象適配器的使用頻率更高,對象適配器模式結(jié)構(gòu)圖如下圖所示:
在對象適配器模式結(jié)構(gòu)圖中包含如下幾個角色:
● Target(目標抽象類):目標抽象類定義客戶所需接口蒙兰,可以是一個抽象類或接口,也可以是具體類。
● Adapter(適配器類):適配器可以調(diào)用另一個接口癞己,作為一個轉(zhuǎn)換器膀斋,對Adaptee和Target進行適配,適配器類是適配器模式的核心痹雅,在對象適配器中仰担,它通過繼承Target并關(guān)聯(lián)一個Adaptee對象使二者產(chǎn)生聯(lián)系。
● Adaptee(適配者類):適配者即被適配的角色绩社,它定義了一個已經(jīng)存在的接口摔蓝,這個接口需要適配,適配者類一般是一個具體類愉耙,包含了客戶希望使用的業(yè)務(wù)方法贮尉,在某些情況下可能沒有適配者類的源代碼。
類適配器模式和對象適配器模式最大的區(qū)別在于適配器和適配者之間的關(guān)系不同朴沿,對象適配器模式中適配器和適配者之間是關(guān)聯(lián)關(guān)系猜谚,而類適配器模式中適配器和適配者是繼承關(guān)系,類適配器模式結(jié)構(gòu)如下圖所示:
由于Java赌渣、C#等語言不支持多重類繼承魏铅,因此類適配器的使用受到很多限制,例如如果目標抽象類Target不是接口坚芜,而是一個類览芳,就無法使用類適配器;此外鸿竖,如果適配者Adaptee為最終(Final)類沧竟,也無法使用類適配器。在Java等面向?qū)ο缶幊陶Z言中缚忧,大部分情況下我們使用的是對象適配器悟泵,類適配器較少使用。
缺省適配器模式是適配器模式的一種變體搔谴,其應(yīng)用也較為廣泛魁袜。
缺省適配器模式(Default Adapter Pattern):當不需要實現(xiàn)一個接口所提供的所有方法時,可先設(shè)計一個抽象類實現(xiàn)該接口敦第,并為接口中每個方法提供一個默認實現(xiàn)(空方法)峰弹,那么該抽象類的子類可以選擇性地覆蓋父類的某些方法來實現(xiàn)需求,它適用于不想使用一個接口中的所有方法的情況芜果,又稱為單接口適配器模式鞠呈。
● ServiceInterface(適配者接口):它是一個接口,通常在該接口中聲明了大量的方法右钾。
● AbstractServiceClass(缺省適配器類):它是缺省適配器模式的核心類蚁吝,使用空方法的形式實現(xiàn)了在ServiceInterface接口中聲明的方法旱爆。通常將它定義為抽象類,因為對它進行實例化沒有任何意義窘茁。
● ConcreteServiceClass(具體業(yè)務(wù)類):它是缺省適配器類的子類怀伦,在沒有引入適配器之前,它需要實現(xiàn)適配者接口山林,因此需要實現(xiàn)在適配者接口中定義的所有方法房待,而對于一些無須使用的方法也不得不提供空實現(xiàn)。在有了缺省適配器之后驼抹,可以直接繼承該適配器類桑孩,根據(jù)需要有選擇性地覆蓋在適配器類中定義的方法。
適配器模式總結(jié)
適配器模式將現(xiàn)有接口轉(zhuǎn)化為客戶類所期望的接口框冀,實現(xiàn)了對現(xiàn)有類的復(fù)用流椒,它是一種使用頻率非常高的設(shè)計模式,在軟件開發(fā)中得以廣泛應(yīng)用明也,在Spring等開源框架宣虾、驅(qū)動程序設(shè)計(如JDBC中的數(shù)據(jù)庫驅(qū)動程序)中也使用了適配器模式。
- 主要優(yōu)點
無論是對象適配器模式還是類適配器模式都具有如下優(yōu)點:
(1) 將目標類和適配者類解耦诡右,通過引入一個適配器類來重用現(xiàn)有的適配者類王带,無須修改原有結(jié)構(gòu)。
(2) 增加了類的透明性和復(fù)用性畔况,將具體的業(yè)務(wù)實現(xiàn)過程封裝在適配者類中滋觉,對于客戶端類而言是透明的比被,而且提高了適配者的復(fù)用性祭陷,同一個適配者類可以在多個不同的系統(tǒng)中復(fù)用苍凛。
(3) 靈活性和擴展性都非常好,通過使用配置文件兵志,可以很方便地更換適配器醇蝴,也可以在不修改原有代碼的基礎(chǔ)上增加新的適配器類,完全符合“開閉原則”想罕。
具體來說悠栓,類適配器模式還有如下優(yōu)點:
由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法按价,使得適配器的靈活性更強闸迷。
對象適配器模式還有如下優(yōu)點:
(1) 一個對象適配器可以把多個不同的適配者適配到同一個目標;
(2) 可以適配一個適配者的子類俘枫,由于適配器和適配者之間是關(guān)聯(lián)關(guān)系,根據(jù)“里氏代換原則”逮走,適配者的子類也可通過該適配器進行適配鸠蚪。
- 主要缺點
類適配器模式的缺點如下:
(1) 對于Java、C#等不支持多重類繼承的語言师溅,一次最多只能適配一個適配者類茅信,不能同時適配多個適配者;
(2) 適配者類不能為最終類墓臭,如在Java中不能為final類蘸鲸,C#中不能為sealed類;
(3) 在Java啃擦、C#等語言中朋其,類適配器模式中的目標抽象類只能為接口,不能為類匾二,其使用有一定的局限性窑多。
對象適配器模式的缺點如下:
與類適配器模式相比仍稀,要在適配器中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個或多個方法埂息,可以先做一個適配者類的子類技潘,將適配者類的方法置換掉,然后再把適配者類的子類當做真正的適配者進行適配千康,實現(xiàn)過程較為復(fù)雜享幽。
- 適用場景
在以下情況下可以考慮使用適配器模式:
(1) 系統(tǒng)需要使用一些現(xiàn)有的類,而這些類的接口(如方法名)不符合系統(tǒng)的需要拾弃,甚至沒有這些類的源代碼值桩。
(2) 想創(chuàng)建一個可以重復(fù)使用的類,用于與一些彼此之間沒有太大關(guān)聯(lián)的一些類砸彬,包括一些可能在將來引進的類一起工作颠毙。
橋接模式-Bridge Pattern
如果軟件系統(tǒng)中某個類存在兩個獨立變化的維度,通過該模式可以將這兩個維度分離出來砂碉,使兩者可以獨立擴展蛀蜜,讓系統(tǒng)更加符合“單一職責原則”。與多層繼承方案不同增蹭,它將兩個獨立變化的維度設(shè)計為兩個獨立的繼承等級結(jié)構(gòu)滴某,并且在抽象層建立一個抽象關(guān)聯(lián),該關(guān)聯(lián)關(guān)系類似一條連接兩個獨立繼承結(jié)構(gòu)的橋滋迈,故名橋接模式霎奢。
橋接模式用一種巧妙的方式處理多層繼承存在的問題,用抽象關(guān)聯(lián)取代了傳統(tǒng)的多層繼承饼灿,將類之間的靜態(tài)繼承關(guān)系轉(zhuǎn)換為動態(tài)的對象組合關(guān)系幕侠,使得系統(tǒng)更加靈活,并易于擴展碍彭,同時有效控制了系統(tǒng)中類的個數(shù)晤硕。橋接定義如下:
橋接模式(Bridge Pattern):將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨立地變化庇忌。它是一種對象結(jié)構(gòu)型模式舞箍,又稱為柄體(Handle and Body)模式或接口(Interface)模式。
橋接模式的結(jié)構(gòu)與其名稱一樣皆疹,存在一條連接兩個繼承等級結(jié)構(gòu)的橋疏橄,橋接模式結(jié)構(gòu)如下圖所示:
在橋接模式結(jié)構(gòu)圖中包含如下幾個角色:
●Abstraction(抽象類):用于定義抽象類的接口,它一般是抽象類而不是接口略就,其中定義了一個Implementor(實現(xiàn)類接口)類型的對象并可以維護該對象捎迫,它與Implementor之間具有關(guān)聯(lián)關(guān)系晃酒,它既可以包含抽象業(yè)務(wù)方法,也可以包含具體業(yè)務(wù)方法立砸。
●RefinedAbstraction(擴充抽象類):擴充由Abstraction定義的接口掖疮,通常情況下它不再是抽象類而是具體類,它實現(xiàn)了在Abstraction中聲明的抽象業(yè)務(wù)方法颗祝,在RefinedAbstraction中可以調(diào)用在Implementor中定義的業(yè)務(wù)方法浊闪。
●Implementor(實現(xiàn)類接口):定義實現(xiàn)類的接口,這個接口不一定要與Abstraction的接口完全一致螺戳,事實上這兩個接口可以完全不同搁宾,一般而言,Implementor接口僅提供基本操作倔幼,而Abstraction定義的接口可能會做更多更復(fù)雜的操作盖腿。Implementor接口對這些基本操作進行了聲明,而具體實現(xiàn)交給其子類损同。通過關(guān)聯(lián)關(guān)系翩腐,在Abstraction中不僅擁有自己的方法,還可以調(diào)用到Implementor中定義的方法膏燃,使用關(guān)聯(lián)關(guān)系來替代繼承關(guān)系茂卦。
●ConcreteImplementor(具體實現(xiàn)類):具體實現(xiàn)Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同實現(xiàn)组哩,在程序運行時等龙,ConcreteImplementor對象將替換其父類對象,提供給抽象類具體的業(yè)務(wù)操作方法伶贰。
在使用橋接模式時蛛砰,我們首先應(yīng)該識別出一個類所具有的兩個獨立變化的維度,將它們設(shè)計為兩個獨立的繼承等級結(jié)構(gòu)黍衙,為兩個維度都提供抽象層泥畅,并建立抽象耦合。通常情況下琅翻,我們將具有兩個獨立變化維度的類的一些普通業(yè)務(wù)方法和與之關(guān)系最密切的維度設(shè)計為“抽象類”層次結(jié)構(gòu)(抽象部分)涯捻,而將另一個維度設(shè)計為“實現(xiàn)類”層次結(jié)構(gòu)(實現(xiàn)部分)。
在軟件開發(fā)中望迎,適配器模式通常可以與橋接模式聯(lián)合使用凌外。適配器模式可以解決兩個已有接口間不兼容問題辩尊,在這種情況下被適配的類往往是一個黑盒子,有時候我們不想也不能改變這個被適配的類康辑,也不能控制其擴展摄欲。適配器模式通常用于現(xiàn)有系統(tǒng)與第三方產(chǎn)品功能的集成轿亮,采用增加適配器的方式將第三方類集成到系統(tǒng)中。橋接模式則不同胸墙,用戶可以通過接口繼承或類繼承的方式來對系統(tǒng)進行擴展我注。
橋接模式和適配器模式用于設(shè)計的不同階段,橋接模式用于系統(tǒng)的初步設(shè)計迟隅,對于存在兩個獨立變化維度的類可以將其分為抽象化和實現(xiàn)化兩個角色但骨,使它們可以分別進行變化;而在初步設(shè)計完成之后智袭,當發(fā)現(xiàn)系統(tǒng)與已有類無法協(xié)同工作時奔缠,可以采用適配器模式。但有時候在設(shè)計初期也需要考慮適配器模式吼野,特別是那些涉及到大量第三方應(yīng)用接口的情況校哎。
橋接模式總結(jié)
橋接模式是設(shè)計Java虛擬機和實現(xiàn)JDBC等驅(qū)動程序的核心模式之一,應(yīng)用較為廣泛瞳步。在軟件開發(fā)中如果一個類或一個系統(tǒng)有多個變化維度時闷哆,都可以嘗試使用橋接模式對其進行設(shè)計。橋接模式為多維度變化的系統(tǒng)提供了一套完整的解決方案单起,并且降低了系統(tǒng)的復(fù)雜度抱怔。
1.主要優(yōu)點
橋接模式的主要優(yōu)點如下:
(1)分離抽象接口及其實現(xiàn)部分。橋接模式使用“對象間的關(guān)聯(lián)關(guān)系”解耦了抽象和實現(xiàn)之間固有的綁定關(guān)系馏臭,使得抽象和實現(xiàn)可以沿著各自的維度來變化野蝇。所謂抽象和實現(xiàn)沿著各自維度的變化,也就是說抽象和實現(xiàn)不再在同一個繼承層次結(jié)構(gòu)中括儒,而是“子類化”它們绕沈,使它們各自都具有自己的子類,以便任何組合子類帮寻,從而獲得多維度組合對象乍狐。
(2)在很多情況下,橋接模式可以取代多層繼承方案固逗,多層繼承方案違背了“單一職責原則”浅蚪,復(fù)用性較差,且類的個數(shù)非常多烫罩,橋接模式是比多層繼承方案更好的解決方法惜傲,它極大減少了子類的個數(shù)。
(3)橋接模式提高了系統(tǒng)的可擴展性贝攒,在兩個變化維度中任意擴展一個維度盗誊,都不需要修改原有系統(tǒng),符合“開閉原則”。
2.主要缺點
橋接模式的主要缺點如下:
(1)橋接模式的使用會增加系統(tǒng)的理解與設(shè)計難度哈踱,由于關(guān)聯(lián)關(guān)系建立在抽象層荒适,要求開發(fā)者一開始就針對抽象層進行設(shè)計與編程。
(2)橋接模式要求正確識別出系統(tǒng)中兩個獨立變化的維度开镣,因此其使用范圍具有一定的局限性刀诬,如何正確識別兩個獨立維度也需要一定的經(jīng)驗積累。
3.適用場景
在以下情況下可以考慮使用橋接模式:
(1)如果一個系統(tǒng)需要在抽象化和具體化之間增加更多的靈活性邪财,避免在兩個層次之間建立靜態(tài)的繼承關(guān)系陕壹,通過橋接模式可以使它們在抽象層建立一個關(guān)聯(lián)關(guān)系。
(2)“抽象部分”和“實現(xiàn)部分”可以以繼承的方式獨立擴展而互不影響卧蜓,在程序運行時可以動態(tài)將一個抽象化子類的對象和一個實現(xiàn)化子類的對象進行組合帐要,即系統(tǒng)需要對抽象化角色和實現(xiàn)化角色進行動態(tài)耦合。
(3)一個類存在兩個(或多個)獨立變化的維度弥奸,且這兩個(或多個)維度都需要獨立進行擴展榨惠。
(4)對于那些不希望使用繼承或因為多層繼承導(dǎo)致系統(tǒng)類的個數(shù)急劇增加的系統(tǒng),橋接模式尤為適用盛霎。
組合模式-Composite Pattern
對于樹形結(jié)構(gòu)赠橙,當容器對象(如文件夾)的某一個方法被調(diào)用時,將遍歷整個樹形結(jié)構(gòu)愤炸,尋找也包含這個方法的成員對象(可以是容器對象期揪,也可以是葉子對象)并調(diào)用執(zhí)行,牽一而動百规个,其中使用了遞歸調(diào)用的機制來對整個結(jié)構(gòu)進行處理凤薛。由于容器對象和葉子對象在功能上的區(qū)別,在使用這些對象的代碼中必須有區(qū)別地對待容器對象和葉子對象诞仓,而實際上大多數(shù)情況下我們希望一致地處理它們缤苫,因為對于這些對象的區(qū)別對待將會使得程序非常復(fù)雜。組合模式為解決此類問題而誕生墅拭,它可以讓葉子對象和容器對象的使用具有一致性活玲。
組合模式(Composite Pattern):組合多個對象形成樹形結(jié)構(gòu)以表示具有“整體—部分”關(guān)系的層次結(jié)構(gòu)。組合模式對單個對象(即葉子對象)和組合對象(即容器對象)的使用具有一致性谍婉,組合模式又可以稱為“整體—部分”(Part-Whole)模式舒憾,它是一種對象結(jié)構(gòu)型模式。
在組合模式中引入了抽象構(gòu)件類Component穗熬,它是所有容器類和葉子類的公共父類镀迂,客戶端針對Component進行編程。組合模式結(jié)構(gòu)如下圖所示:
在組合模式結(jié)構(gòu)圖中包含如下幾個角色:
● Component(抽象構(gòu)件):它可以是接口或抽象類唤蔗,為葉子構(gòu)件和容器構(gòu)件對象聲明接口招拙,在該角色中可以包含所有子類共有行為的聲明和實現(xiàn)唧瘾。在抽象構(gòu)件中定義了訪問及管理它的子構(gòu)件的方法,如增加子構(gòu)件别凤、刪除子構(gòu)件、獲取子構(gòu)件等领虹。
● Leaf(葉子構(gòu)件):它在組合結(jié)構(gòu)中表示葉子節(jié)點對象规哪,葉子節(jié)點沒有子節(jié)點,它實現(xiàn)了在抽象構(gòu)件中定義的行為塌衰。對于那些訪問及管理子構(gòu)件的方法诉稍,可以通過異常等方式進行處理。
● Composite(容器構(gòu)件):它在組合結(jié)構(gòu)中表示容器節(jié)點對象最疆,容器節(jié)點包含子節(jié)點杯巨,其子節(jié)點可以是葉子節(jié)點,也可以是容器節(jié)點努酸,它提供一個集合用于存儲子節(jié)點服爷,實現(xiàn)了在抽象構(gòu)件中定義的行為,包括那些訪問及管理子構(gòu)件的方法获诈,在其業(yè)務(wù)方法中可以遞歸調(diào)用其子節(jié)點的業(yè)務(wù)方法仍源。
組合模式的關(guān)鍵是定義了一個抽象構(gòu)件類剖张,它既可以代表葉子升略,又可以代表容器凌受,而客戶端針對該抽象構(gòu)件類進行編程赌厅,無須知道它到底表示的是葉子還是容器擎宝,可以對其進行統(tǒng)一處理砂缩。同時容器對象與抽象構(gòu)件類之間還建立一個聚合關(guān)聯(lián)關(guān)系泵额,在容器對象中既可以包含葉子迫悠,也可以包含容器挟冠,以此實現(xiàn)遞歸組合于购,形成一個樹形結(jié)構(gòu)。
如果不使用組合模式圃郊,客戶端代碼將過多地依賴于容器對象復(fù)雜的內(nèi)部實現(xiàn)結(jié)構(gòu),容器對象內(nèi)部實現(xiàn)結(jié)構(gòu)的變化將引起客戶代碼的頻繁變化魏割,帶來了代碼維護復(fù)雜芍秆、可擴展性差等弊端孟抗。組合模式的引入將在一定程度上解決這些問題迁杨。
下面通過簡單的示例代碼來分析組合模式的各個角色的用途和實現(xiàn)。對于組合模式中的抽象構(gòu)件角色凄硼,其典型代碼如下所示:
abstract class Component {
public abstract void add(Component c); //增加成員
public abstract void remove(Component c); //刪除成員
public abstract Component getChild(int i); //獲取成員
public abstract void operation(); //業(yè)務(wù)方法
}
一般將抽象構(gòu)件類設(shè)計為接口或抽象類铅协,將所有子類共有方法的聲明和實現(xiàn)放在抽象構(gòu)件類中。對于客戶端而言摊沉,將針對抽象構(gòu)件編程狐史,而無須關(guān)心其具體子類是容器構(gòu)件還是葉子構(gòu)件。
如果繼承抽象構(gòu)件的是葉子構(gòu)件说墨,則其典型代碼如下所示:
class Leaf extends Component {
public void add(Component c) {
//異常處理或錯誤提示
}
public void remove(Component c) {
//異常處理或錯誤提示
}
public Component getChild(int i) {
//異常處理或錯誤提示
return null;
}
public void operation() {
//葉子構(gòu)件具體業(yè)務(wù)方法的實現(xiàn)
}
}
在容器構(gòu)件中實現(xiàn)了在抽象構(gòu)件中聲明的所有方法骏全,既包括業(yè)務(wù)方法,也包括用于訪問和管理成員子構(gòu)件的方法婉刀,如add()吟温、remove()和getChild()等方法。需要注意的是在實現(xiàn)具體業(yè)務(wù)方法時突颊,由于容器構(gòu)件充當?shù)氖侨萜鹘巧澈溃蓡T構(gòu)件潘悼,因此它將調(diào)用其成員構(gòu)件的業(yè)務(wù)方法。在組合模式結(jié)構(gòu)中爬橡,由于容器構(gòu)件中仍然可以包含容器構(gòu)件治唤,因此在對容器構(gòu)件進行處理時需要使用遞歸算法,即在容器構(gòu)件的operation()方法中遞歸調(diào)用其成員構(gòu)件的operation()方法糙申。
在使用組合模式時宾添,根據(jù)抽象構(gòu)件類的定義形式,我們可將組合模式分為透明組合模式和安全組合模式兩種形式:
(1) 透明組合模式
透明組合模式中柜裸,抽象構(gòu)件Component中聲明了所有用于管理成員對象的方法缕陕,包括add()、remove()以及getChild()等方法疙挺,這樣做的好處是確保所有的構(gòu)件類都有相同的接口扛邑。在客戶端看來,葉子對象與容器對象所提供的方法是一致的铐然,客戶端可以相同地對待所有的對象蔬崩。透明組合模式也是組合模式的標準形式,雖然上面的解決方案一在客戶端可以有不透明的實現(xiàn)方法搀暑,但是由于在抽象構(gòu)件中包含add()沥阳、remove()等方法,因此它還是透明組合模式自点,透明組合模式的完整結(jié)構(gòu)如下圖所示:
透明組合模式的缺點是不夠安全桐罕,因為葉子對象和容器對象在本質(zhì)上是有區(qū)別的。葉子對象不可能有下一個層次的對象樟氢,即不可能包含成員對象冈绊,因此為其提供add()、remove()以及getChild()等方法是沒有意義的埠啃,這在編譯階段不會出錯死宣,但在運行階段如果調(diào)用這些方法可能會出錯(如果沒有提供相應(yīng)的錯誤處理代碼)。
(2) 安全組合模式
安全組合模式中碴开,在抽象構(gòu)件Component中沒有聲明任何用于管理成員對象的方法毅该,而是在Composite類中聲明并實現(xiàn)這些方法。這種做法是安全的潦牛,因為根本不向葉子對象提供這些管理成員對象的方法眶掌,對于葉子對象,客戶端不可能調(diào)用到這些方法巴碗,這就是解決方案二所采用的實現(xiàn)方式朴爬。安全組合模式的結(jié)構(gòu)如下圖所示:
安全組合模式的缺點是不夠透明,因為葉子構(gòu)件和容器構(gòu)件具有不同的方法橡淆,且容器構(gòu)件中那些用于管理成員對象的方法沒有在抽象構(gòu)件類中定義召噩,因此客戶端不能完全針對抽象編程母赵,必須有區(qū)別地對待葉子構(gòu)件和容器構(gòu)件。在實際應(yīng)用中具滴,安全組合模式的使用頻率也非常高凹嘲,在Java AWT中使用的組合模式就是安全組合模式。
組合模式總結(jié)
組合模式使用面向?qū)ο蟮乃枷雭韺崿F(xiàn)樹形結(jié)構(gòu)的構(gòu)建與處理构韵,描述了如何將容器對象和葉子對象進行遞歸組合周蹭,實現(xiàn)簡單,靈活性好疲恢。由于在軟件開發(fā)中存在大量的樹形結(jié)構(gòu)凶朗,因此組合模式是一種使用頻率較高的結(jié)構(gòu)型設(shè)計模式。在XML解析显拳、組織結(jié)構(gòu)樹處理俱尼、文件系統(tǒng)設(shè)計等領(lǐng)域,組合模式都得到了廣泛應(yīng)用萎攒。
- 主要優(yōu)點
組合模式的主要優(yōu)點如下:
(1) 組合模式可以清楚地定義分層次的復(fù)雜對象,表示對象的全部或部分層次矛绘,它讓客戶端忽略了層次的差異耍休,方便對整個層次結(jié)構(gòu)進行控制。
(2) 客戶端可以一致地使用一個組合結(jié)構(gòu)或其中單個對象货矮,不必關(guān)心處理的是單個對象還是整個組合結(jié)構(gòu)羊精,簡化了客戶端代碼。
(3) 在組合模式中增加新的容器構(gòu)件和葉子構(gòu)件都很方便囚玫,無須對現(xiàn)有類庫進行任何修改喧锦,符合“開閉原則”。
(4) 組合模式為樹形結(jié)構(gòu)的面向?qū)ο髮崿F(xiàn)提供了一種靈活的解決方案抓督,通過葉子對象和容器對象的遞歸組合燃少,可以形成復(fù)雜的樹形結(jié)構(gòu),但對樹形結(jié)構(gòu)的控制卻非常簡單铃在。 - 主要缺點
組合模式的主要缺點如下:
在增加新構(gòu)件時很難對容器中的構(gòu)件類型進行限制阵具。有時候我們希望一個容器中只能有某些特定類型的對象,例如在某個文件夾中只能包含文本文件定铜,使用組合模式時阳液,不能依賴類型系統(tǒng)來施加這些約束,因為它們都來自于相同的抽象層揣炕,在這種情況下帘皿,必須通過在運行時進行類型檢查來實現(xiàn),這個實現(xiàn)過程較為復(fù)雜畸陡。 - 適用場景
在以下情況下可以考慮使用組合模式:
(1) 在具有整體和部分的層次結(jié)構(gòu)中鹰溜,希望通過一種方式忽略整體與部分的差異虽填,客戶端可以一致地對待它們。
(2) 在一個使用面向?qū)ο笳Z言開發(fā)的系統(tǒng)中需要處理一個樹形結(jié)構(gòu)奉狈。
(3) 在一個系統(tǒng)中能夠分離出葉子對象和容器對象卤唉,而且它們的類型不固定,需要增加一些新的類型仁期。
裝飾模式-Decorator Pattern
裝飾模式可以在不改變一個對象本身功能的基礎(chǔ)上給對象增加額外的新行為桑驱,在現(xiàn)實生活中,這種情況也到處存在跛蛋,例如一張照片熬的,我們可以不改變照片本身,給它增加一個相框赊级,使得它具有防潮的功能押框,而且用戶可以根據(jù)需要給它增加不同類型的相框,甚至可以在一個小相框的外面再套一個大相框理逊。
裝飾模式是一種用于替代繼承的技術(shù)橡伞,它通過一種無須定義子類的方式來給對象動態(tài)增加職責,使用對象之間的關(guān)聯(lián)關(guān)系取代類之間的繼承關(guān)系晋被。在裝飾模式中引入了裝飾類兑徘,在裝飾類中既可以調(diào)用待裝飾的原有類的方法,還可以增加新的方法羡洛,以擴充原有類的功能挂脑。
裝飾模式(Decorator Pattern):動態(tài)地給一個對象增加一些額外的職責,就增加對象功能來說欲侮,裝飾模式比生成子類實現(xiàn)更為靈活崭闲。裝飾模式是一種對象結(jié)構(gòu)型模式。
在裝飾模式中威蕉,為了讓系統(tǒng)具有更好的靈活性和可擴展性刁俭,我們通常會定義一個抽象裝飾類,而將具體的裝飾類作為它的子類韧涨,裝飾模式結(jié)構(gòu)如下圖所示:
在裝飾模式結(jié)構(gòu)圖中包含如下幾個角色:
● Component(抽象構(gòu)件):它是具體構(gòu)件和抽象裝飾類的共同父類薄翅,聲明了在具體構(gòu)件中實現(xiàn)的業(yè)務(wù)方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之后的對象氓奈,實現(xiàn)客戶端的透明操作翘魄。
● ConcreteComponent(具體構(gòu)件):它是抽象構(gòu)件類的子類,用于定義具體的構(gòu)件對象舀奶,實現(xiàn)了在抽象構(gòu)件中聲明的方法暑竟,裝飾器可以給它增加額外的職責(方法)。
● Decorator(抽象裝飾類):它也是抽象構(gòu)件類的子類,用于給具體構(gòu)件增加職責但荤,但是具體職責在其子類中實現(xiàn)罗岖。它維護一個指向抽象構(gòu)件對象的引用,通過該引用可以調(diào)用裝飾之前構(gòu)件對象的方法腹躁,并通過其子類擴展該方法桑包,以達到裝飾的目的。
● ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類纺非,負責向構(gòu)件添加新的職責哑了。每一個具體裝飾類都定義了一些新的行為,它可以調(diào)用在抽象裝飾類中定義的方法烧颖,并可以增加新的方法用以擴充對象的行為弱左。
由于具體構(gòu)件類和裝飾類都實現(xiàn)了相同的抽象構(gòu)件接口,因此裝飾模式以對客戶透明的方式動態(tài)地給一個對象附加上更多的責任炕淮,換言之拆火,客戶端并不會覺得對象在裝飾前和裝飾后有什么不同。裝飾模式可以在不需要創(chuàng)造更多子類的情況下涂圆,將對象的功能加以擴展们镜。
透明裝飾模式與半透明裝飾模式
在實際使用過程中,由于新增行為可能需要單獨調(diào)用(調(diào)用具體裝飾器聲明的方法)润歉,因此這種形式的裝飾模式也經(jīng)常出現(xiàn)憎账,這種裝飾模式被稱為半透明(Semi-transparent)裝飾模式,而標準的裝飾模式是透明(Transparent)裝飾模式卡辰。下面我們對這兩種裝飾模式進行較為詳細的介紹:
(1)透明裝飾模式
裝飾模式的透明性要求客戶端程序不應(yīng)該將對象聲明為具體構(gòu)件類型或具體裝飾類型,而應(yīng)該全部聲明為抽象構(gòu)件類型邪意。對于客戶端而言九妈,具體構(gòu)件對象和具體裝飾對象沒有任何區(qū)別。也就是抽象裝飾器中沒有單獨聲明方法雾鬼。
透明裝飾模式可以讓客戶端透明地使用裝飾之前的對象和裝飾之后的對象萌朱,無須關(guān)心它們的區(qū)別,此外抢呆,還可以對一個已裝飾過的對象進行多次裝飾茧吊,得到更為復(fù)雜嚼松、功能更為強大的對象。
(2)半透明裝飾模式
透明裝飾模式的設(shè)計難度較大翠霍,而且有時我們需要單獨調(diào)用新增的業(yè)務(wù)方法。為了能夠調(diào)用到新增方法蠢莺,我們不得不用具體裝飾類型來定義裝飾之后的對象寒匙,而具體構(gòu)件類型還是可以使用抽象構(gòu)件類型來定義,這種裝飾模式即為半透明裝飾模式躏将,也就是說锄弱,對于客戶端而言考蕾,具體構(gòu)件類型無須關(guān)心,是透明的会宪;如果具體裝飾類型必須指定肖卧,那這就是不透明的。
半透明裝飾模式可以給系統(tǒng)帶來更多的靈活性掸鹅,設(shè)計相對簡單塞帐,使用起來也非常方便;但是其最大的缺點在于不能實現(xiàn)對同一個對象的多次裝飾河劝,而且客戶端需要有區(qū)別地對待裝飾之前的對象和裝飾之后的對象壁榕。
裝飾模式注意事項
在使用裝飾模式時,通常我們需要注意以下幾個問題:
(1) 盡量保持裝飾類的接口與被裝飾類的接口相同赎瞎,這樣牌里,對于客戶端而言,無論是裝飾之前的對象還是裝飾之后的對象都可以一致對待务甥。這也就是說牡辽,在可能的情況下,我們應(yīng)該盡量使用透明裝飾模式敞临。
(2) 盡量保持具體構(gòu)件類ConcreteComponent是一個“輕”類态辛,也就是說不要把太多的行為放在具體構(gòu)件類中,我們可以通過裝飾類對其進行擴展挺尿。
(3) 如果只有一個具體構(gòu)件類奏黑,那么抽象裝飾類可以作為該具體構(gòu)件類的直接子類。
裝飾模式總結(jié)
裝飾模式降低了系統(tǒng)的耦合度编矾,可以動態(tài)增加或刪除對象的職責熟史,并使得需要裝飾的具體構(gòu)件類和具體裝飾類可以獨立變化,以便增加新的具體構(gòu)件類和具體裝飾類窄俏。在軟件開發(fā)中蹂匹,裝飾模式應(yīng)用較為廣泛,例如在JavaIO中的輸入流和輸出流的設(shè)計凹蜈、javax.swing包中一些圖形界面構(gòu)件功能的增強等地方都運用了裝飾模式限寞。
1.主要優(yōu)點
裝飾模式的主要優(yōu)點如下:
(1) 對于擴展一個對象的功能,裝飾模式比繼承更加靈活性仰坦,不會導(dǎo)致類的個數(shù)急劇增加履植。
(2) 可以通過一種動態(tài)的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的具體裝飾類悄晃,從而實現(xiàn)不同的行為静尼。
(3) 可以對一個對象進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創(chuàng)造出很多不同行為的組合鼠渺,得到功能更為強大的對象鸭巴。
(4) 具體構(gòu)件類與具體裝飾類可以獨立變化,用戶可以根據(jù)需要增加新的具體構(gòu)件類和具體裝飾類拦盹,原有類庫代碼無須改變鹃祖,符合“開閉原則”。
2.主要缺點
裝飾模式的主要缺點如下:
(1) 使用裝飾模式進行系統(tǒng)設(shè)計時將產(chǎn)生很多小對象普舆,這些對象的區(qū)別在于它們之間相互連接的方式有所不同恬口,而不是它們的類或者屬性值有所不同,大量小對象的產(chǎn)生勢必會占用更多的系統(tǒng)資源沼侣,在一定程序上影響程序的性能祖能。
(2) 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味著比繼承更加易于出錯蛾洛,排錯也很困難养铸,對于多次裝飾的對象,調(diào)試時尋找錯誤可能需要逐級排查轧膘,較為繁瑣钞螟。
3.適用場景
在以下情況下可以考慮使用裝飾模式:
(1) 在不影響其他對象的情況下,以動態(tài)谎碍、透明的方式給單個對象添加職責鳞滨。
(2) 當不能采用繼承的方式對系統(tǒng)進行擴展或者采用繼承不利于系統(tǒng)擴展和維護時可以使用裝飾模式。不能采用繼承的情況主要有兩類:第一類是系統(tǒng)中存在大量獨立的擴展蟆淀,為支持每一種擴展或者擴展之間的組合將產(chǎn)生大量的子類拯啦,使得子類數(shù)目呈爆炸性增長;第二類是因為類已定義為不能被繼承(如Java語言中的final類)熔任。
外觀模式-Facade Pattern
在軟件開發(fā)中褒链,有時候為了完成一項較為復(fù)雜的功能,一個客戶類需要和多個業(yè)務(wù)類交互笋敞,而這些需要交互的業(yè)務(wù)類經(jīng)常會作為一個整體出現(xiàn),由于涉及到的類比較多荠瘪,導(dǎo)致使用時代碼較為復(fù)雜夯巷,此時,特別需要一個類似服務(wù)員一樣的角色哀墓,由它來負責和多個業(yè)務(wù)類進行交互趁餐,而客戶類只需與該類交互。外觀模式通過引入一個新的外觀類(Facade)來實現(xiàn)該功能篮绰,外觀類充當了軟件系統(tǒng)中的“服務(wù)員”后雷,它為多個業(yè)務(wù)類的調(diào)用提供了一個統(tǒng)一的入口,簡化了類與類之間的交互。在外觀模式中臀突,那些需要交互的業(yè)務(wù)類被稱為子系統(tǒng)(Subsystem)勉抓。如果沒有外觀類,那么每個客戶類需要和多個子系統(tǒng)之間進行復(fù)雜的交互候学,系統(tǒng)的耦合度將很大藕筋;而引入外觀類之后,客戶類只需要直接與外觀類交互梳码,客戶類與子系統(tǒng)之間原有的復(fù)雜引用關(guān)系由外觀類來實現(xiàn)隐圾,從而降低了系統(tǒng)的耦合度。
外觀模式中掰茶,一個子系統(tǒng)的外部與其內(nèi)部的通信通過一個統(tǒng)一的外觀類進行暇藏,外觀類將客戶類與子系統(tǒng)的內(nèi)部復(fù)雜性分隔開,使得客戶類只需要與外觀角色打交道濒蒋,而不需要與子系統(tǒng)內(nèi)部的很多對象打交道盐碱。
外觀模式:為子系統(tǒng)中的一組接口提供一個統(tǒng)一的入口。外觀模式定義了一個高層接口啊胶,這個接口使得這一子系統(tǒng)更加容易使用甸各。
下圖所示的類圖可以作為描述外觀模式的結(jié)構(gòu)圖:
外觀模式包含如下兩個角色:
(1) Facade(外觀角色):在客戶端可以調(diào)用它的方法,在外觀角色中可以知道相關(guān)的(一個或者多個)子系統(tǒng)的功能和責任焰坪;在正常情況下趣倾,它將所有從客戶端發(fā)來的請求委派到相應(yīng)的子系統(tǒng)去,傳遞給相應(yīng)的子系統(tǒng)對象處理某饰。
(2) SubSystem(子系統(tǒng)角色):在軟件系統(tǒng)中可以有一個或者多個子系統(tǒng)角色儒恋,每一個子系統(tǒng)可以不是一個單獨的類,而是一個類的集合黔漂,它實現(xiàn)子系統(tǒng)的功能诫尽;每一個子系統(tǒng)都可以被客戶端直接調(diào)用,或者被外觀角色調(diào)用炬守,它處理由外觀類傳過來的請求牧嫉;子系統(tǒng)并不知道外觀的存在,對于子系統(tǒng)而言减途,外觀角色僅僅是另外一個客戶端而已酣藻。
由于在外觀類中維持了對子系統(tǒng)對象的引用,客戶端可以通過外觀類來間接調(diào)用子系統(tǒng)對象的業(yè)務(wù)方法鳍置,而無須與子系統(tǒng)對象直接交互辽剧。引入外觀類后,客戶端代碼變得非常簡單税产。
在標準的外觀模式結(jié)構(gòu)圖中怕轿,如果需要增加偷崩、刪除或更換與外觀類交互的子系統(tǒng)類,必須修改外觀類或客戶端的源代碼撞羽,這將違背開閉原則阐斜,因此可以通過引入抽象外觀類來對系統(tǒng)進行改進,在一定程度上可以解決該問題放吩。在引入抽象外觀類之后智听,客戶端可以針對抽象外觀類進行編程,對于新的業(yè)務(wù)需求渡紫,不需要修改原有外觀類到推,而對應(yīng)增加一個新的具體外觀類,由新的具體外觀類來關(guān)聯(lián)新的子系統(tǒng)對象惕澎,同時通過修改配置文件來達到不修改任何源代碼并更換外觀類的目的莉测。
如何在不修改客戶端代碼的前提下使用新的外觀類呢?解決方法之一是:引入一個抽象外觀類唧喉,客戶端針對抽象外觀類編程捣卤,而在運行時再確定具體外觀類。
外觀模式總結(jié)
外觀模式是一種使用頻率非常高的設(shè)計模式八孝,它通過引入一個外觀角色來簡化客戶端與子系統(tǒng)之間的交互董朝,為復(fù)雜的子系統(tǒng)調(diào)用提供一個統(tǒng)一的入口,使子系統(tǒng)與客戶端的耦合度降低干跛,且客戶端調(diào)用非常方便子姜。外觀模式并不給系統(tǒng)增加任何新功能,它僅僅是簡化調(diào)用接口楼入。在幾乎所有的軟件中都能夠找到外觀模式的應(yīng)用哥捕,如絕大多數(shù)B/S系統(tǒng)都有一個首頁或者導(dǎo)航頁面,大部分C/S系統(tǒng)都提供了菜單或者工具欄嘉熊,在這里遥赚,首頁和導(dǎo)航頁面就是B/S系統(tǒng)的外觀角色,而菜單和工具欄就是C/S系統(tǒng)的外觀角色阐肤,通過它們用戶可以快速訪問子系統(tǒng)凫佛,降低了系統(tǒng)的復(fù)雜程度。所有涉及到與多個業(yè)務(wù)對象交互的場景都可以考慮使用外觀模式進行重構(gòu)孕惜。
- 優(yōu)點
外觀模式的主要優(yōu)點如下:
(1) 它對客戶端屏蔽了子系統(tǒng)組件愧薛,減少了客戶端所需處理的對象數(shù)目,并使得子系統(tǒng)使用起來更加容易诊赊。通過引入外觀模式厚满,客戶端代碼將變得很簡單府瞄,與之關(guān)聯(lián)的對象也很少碧磅。
(2) 它實現(xiàn)了子系統(tǒng)與客戶端之間的松耦合關(guān)系碘箍,這使得子系統(tǒng)的變化不會影響到調(diào)用它的客戶端,只需要調(diào)整外觀類即可鲸郊。
(3) 一個子系統(tǒng)的修改對其他子系統(tǒng)沒有任何影響丰榴,而且子系統(tǒng)內(nèi)部變化也不會影響到外觀對象。
2.缺點
外觀模式的主要缺點如下:
(1) 不能很好地限制客戶端直接使用子系統(tǒng)類秆撮,如果對客戶端訪問子系統(tǒng)類做太多的限制則減少了可變性和靈活性四濒。
(2) 如果設(shè)計不當,增加新的子系統(tǒng)可能需要修改外觀類的源代碼职辨,違背了開閉原則盗蟆。 - 模式適用場景
在以下情況下可以考慮使用外觀模式:
(1) 當要為訪問一系列復(fù)雜的子系統(tǒng)提供一個簡單入口時可以使用外觀模式。
(2) 客戶端程序與多個子系統(tǒng)之間存在很大的依賴性舒裤。引入外觀類可以將子系統(tǒng)與客戶端解耦喳资,從而提高子系統(tǒng)的獨立性和可移植性。
(3) 在層次化結(jié)構(gòu)中腾供,可以使用外觀模式定義系統(tǒng)中每一層的入口仆邓,層與層之間不直接產(chǎn)生聯(lián)系,而通過外觀類建立聯(lián)系伴鳖,降低層之間的耦合度节值。
享元模式-Flyweight Pattern
當一個軟件系統(tǒng)在運行時產(chǎn)生的對象數(shù)量太多,將導(dǎo)致運行代價過高榜聂,帶來系統(tǒng)性能下降等問題嚷往。例如在一個文本字符串中存在很多重復(fù)的字符攘烛,如果每一個字符都用一個單獨的對象來表示,將會占用較多的內(nèi)存空間,那么我們?nèi)绾稳ケ苊庀到y(tǒng)中出現(xiàn)大量相同或相似的對象晤锥,同時又不影響客戶端程序通過面向?qū)ο蟮姆绞綄@些對象進行操作?享元模式正為解決這一類問題而誕生吼渡。享元模式通過共享技術(shù)實現(xiàn)相同或相似對象的重用蛤肌,在邏輯上每一個出現(xiàn)的字符都有一個對象與之對應(yīng),然而在物理上它們卻共享同一個享元對象瘤礁,這個對象可以出現(xiàn)在一個字符串的不同地方阳懂,相同的字符對象都指向同一個實例,在享元模式中柜思,存儲這些共享實例對象的地方稱為享元池(Flyweight Pool)岩调。我們可以針對每一個不同的字符創(chuàng)建一個享元對象,將其放在享元池中赡盘,需要時再從享元池取出号枕。
享元模式以共享的方式高效地支持大量細粒度對象的重用,享元對象能做到共享的關(guān)鍵是區(qū)分了內(nèi)部狀態(tài)(Intrinsic State)和外部狀態(tài)(Extrinsic State)陨享。下面將對享元的內(nèi)部狀態(tài)和外部狀態(tài)進行簡單的介紹:
(1) 內(nèi)部狀態(tài)是存儲在享元對象內(nèi)部并且不會隨環(huán)境改變而改變的狀態(tài)葱淳,內(nèi)部狀態(tài)可以共享钝腺。如字符的內(nèi)容,不會隨外部環(huán)境的變化而變化赞厕,無論在任何環(huán)境下字符“a”始終是“a”艳狐,都不會變成“b”。
(2) 外部狀態(tài)是隨環(huán)境改變而改變的皿桑、不可以共享的狀態(tài)毫目。享元對象的外部狀態(tài)通常由客戶端保存,并在享元對象被創(chuàng)建之后诲侮,需要使用的時候再傳入到享元對象內(nèi)部镀虐。一個外部狀態(tài)與另一個外部狀態(tài)之間是相互獨立的。如字符的顏色沟绪,可以在不同的地方有不同的顏色粉私,例如有的“a”是紅色的,有的“a”是綠色的近零,字符的大小也是如此诺核,有的“a”是五號字,有的“a”是四號字久信。而且字符的顏色和大小是兩個獨立的外部狀態(tài)窖杀,它們可以獨立變化,相互之間沒有影響裙士,客戶端可以在使用時將外部狀態(tài)注入享元對象中入客。
正因為區(qū)分了內(nèi)部狀態(tài)和外部狀態(tài),我們可以將具有相同內(nèi)部狀態(tài)的對象存儲在享元池中腿椎,享元池中的對象是可以實現(xiàn)共享的桌硫,需要的時候就將對象從享元池中取出,實現(xiàn)對象的復(fù)用啃炸。通過向取出的對象注入不同的外部狀態(tài)铆隘,可以得到一系列相似的對象,而這些對象在內(nèi)存中實際上只存儲一份南用。
享元模式(Flyweight Pattern):運用共享技術(shù)有效地支持大量細粒度對象的復(fù)用膀钠。系統(tǒng)只使用少量的對象,而這些對象都很相似裹虫,狀態(tài)變化很小肿嘲,可以實現(xiàn)對象的多次復(fù)用。由于享元模式要求能夠共享的對象必須是細粒度對象筑公,因此它又稱為輕量級模式雳窟,它是一種對象結(jié)構(gòu)型模式。
享元模式結(jié)構(gòu)較為復(fù)雜匣屡,一般結(jié)合工廠模式一起使用封救,在它的結(jié)構(gòu)圖中包含了一個享元工廠類际长,其結(jié)構(gòu)圖如下圖所示
在享元模式結(jié)構(gòu)圖中包含如下幾個角色:
● Flyweight(抽象享元類):通常是一個接口或抽象類,在抽象享元類中聲明了具體享元類公共的方法兴泥,這些方法可以向外界提供享元對象的內(nèi)部數(shù)據(jù)(內(nèi)部狀態(tài)),同時也可以通過這些方法來設(shè)置外部數(shù)據(jù)(外部狀態(tài))虾宇。
● ConcreteFlyweight(具體享元類):它實現(xiàn)了抽象享元類搓彻,其實例稱為享元對象;在具體享元類中為內(nèi)部狀態(tài)提供了存儲空間嘱朽。通常我們可以結(jié)合單例模式來設(shè)計具體享元類旭贬,為每一個具體享元類提供唯一的享元對象。
● UnsharedConcreteFlyweight(非共享具體享元類):并不是所有的抽象享元類的子類都需要被共享搪泳,不能被共享的子類可設(shè)計為非共享具體享元類稀轨;當需要一個非共享具體享元類的對象時可以直接通過實例化創(chuàng)建。
● FlyweightFactory(享元工廠類):享元工廠類用于創(chuàng)建并管理享元對象岸军,它針對抽象享元類編程奋刽,將各種類型的具體享元對象存儲在一個享元池中,享元池一般設(shè)計為一個存儲“鍵值對”的集合(也可以是其他類型的集合)艰赞,可以結(jié)合工廠模式進行設(shè)計佣谐;當用戶請求一個具體享元對象時,享元工廠提供一個存儲在享元池中已創(chuàng)建的實例或者創(chuàng)建一個新的實例(如果不存在的話)方妖,返回新創(chuàng)建的實例并將其存儲在享元池中狭魂。
在享元模式中引入了享元工廠類,享元工廠類的作用在于提供一個用于存儲享元對象的享元池党觅,當用戶需要對象時雌澄,首先從享元池中獲取,如果享元池中不存在杯瞻,則創(chuàng)建一個新的享元對象返回給用戶镐牺,并在享元池中保存該新增對象。典型的享元工廠類的代碼如下:
class FlyweightFactory {
//定義一個HashMap用于存儲享元對象魁莉,實現(xiàn)享元池
private HashMap flyweights = newHashMap();
public Flyweight getFlyweight(String key){
//如果對象存在任柜,則直接從享元池獲取
if(flyweights.containsKey(key)){
return(Flyweight)flyweights.get(key);
}
//如果對象不存在,先創(chuàng)建一個新的對象添加到享元池中沛厨,然后返回
else {
Flyweight fw = newConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
享元類的設(shè)計是享元模式的要點之一宙地,在享元類中要將內(nèi)部狀態(tài)和外部狀態(tài)分開處理,通常將內(nèi)部狀態(tài)作為享元類的成員變量逆皮,而外部狀態(tài)通過注入的方式添加到享元類中宅粥。典型的享元類代碼如下所示:
class Flyweight {
//內(nèi)部狀態(tài)intrinsicState作為成員變量,同一個享元對象其內(nèi)部狀態(tài)是一致的
private String intrinsicState;
public Flyweight(String intrinsicState) {
this.intrinsicState=intrinsicState;
}
//外部狀態(tài)extrinsicState在使用時由外部設(shè)置电谣,不保存在享元對象中秽梅,即使是同一個對象抹蚀,在每一次調(diào)用時也可以傳入不同的外部狀態(tài)
public void operation(String extrinsicState) {
......
}
}
單純享元模式和復(fù)合享元模式
標準的享元模式結(jié)構(gòu)圖中既包含可以共享的具體享元類,也包含不可以共享的非共享具體享元類企垦。但是在實際使用過程中环壤,我們有時候會用到兩種特殊的享元模式:單純享元模式和復(fù)合享元模式,下面將對這兩種特殊的享元模式進行簡單的介紹:
1.單純享元模式
在單純享元模式中钞诡,所有的具體享元類都是可以共享的郑现,不存在非共享具體享元類。單純享元模式的結(jié)構(gòu)如下圖所示:
2.復(fù)合享元模式
將一些單純享元對象使用組合模式加以組合荧降,還可以形成復(fù)合享元對象接箫,這樣的復(fù)合享元對象本身不能共享,但是它們可以分解成單純享元對象朵诫,而后者則可以共享辛友。復(fù)合享元模式的結(jié)構(gòu)如下圖所示:
通過復(fù)合享元模式,可以確保復(fù)合享元類CompositeConcreteFlyweight中所包含的每個單純享元類ConcreteFlyweight都具有相同的外部狀態(tài)剪返,而這些單純享元的內(nèi)部狀態(tài)往往可以不同废累。如果希望為多個內(nèi)部狀態(tài)不同的享元對象設(shè)置相同的外部狀態(tài),可以考慮使用復(fù)合享元模式脱盲。
享元模式總結(jié)
當系統(tǒng)中存在大量相同或者相似的對象時九默,享元模式是一種較好的解決方案,它通過共享技術(shù)實現(xiàn)相同或相似的細粒度對象的復(fù)用宾毒,從而節(jié)約了內(nèi)存空間驼修,提高了系統(tǒng)性能。相比其他結(jié)構(gòu)型設(shè)計模式诈铛,享元模式的使用頻率并不算太高乙各,但是作為一種以“節(jié)約內(nèi)存,提高性能”為出發(fā)點的設(shè)計模式幢竹,它在軟件開發(fā)中還是得到了一定程度的應(yīng)用耳峦。
1.主要優(yōu)點
享元模式的主要優(yōu)點如下:
(1) 可以極大減少內(nèi)存中對象的數(shù)量,使得相同或相似對象在內(nèi)存中只保存一份焕毫,從而可以節(jié)約系統(tǒng)資源蹲坷,提高系統(tǒng)性能。
(2) 享元模式的外部狀態(tài)相對獨立邑飒,而且不會影響其內(nèi)部狀態(tài)循签,從而使得享元對象可以在不同的環(huán)境中被共享。
2.主要缺點
享元模式的主要缺點如下:
(1) 享元模式使得系統(tǒng)變得復(fù)雜疙咸,需要分離出內(nèi)部狀態(tài)和外部狀態(tài)县匠,這使得程序的邏輯復(fù)雜化。
(2) 為了使對象可以共享,享元模式需要將享元對象的部分狀態(tài)外部化乞旦,而讀取外部狀態(tài)將使得運行時間變長贼穆。
3.適用場景
在以下情況下可以考慮使用享元模式:
(1) 一個系統(tǒng)有大量相同或者相似的對象,造成內(nèi)存的大量耗費兰粉。
(2) 對象的大部分狀態(tài)都可以外部化故痊,可以將這些外部狀態(tài)傳入對象中。
(3) 在使用享元模式時需要維護一個存儲享元對象的享元池玖姑,而這需要耗費一定的系統(tǒng)資源愕秫,因此,應(yīng)當在需要多次重復(fù)使用享元對象時才值得使用享元模式客峭。
代理模式-Proxy Pattern
代理模式:給某一個對象提供一個代理或占位符,并由代理對象來控制對原對象的訪問抡柿。在代理模式中引入了一個新的代理對象舔琅,代理對象在客戶端對象和目標對象之間起到中介的作用,它去掉客戶不能看到的內(nèi)容和服務(wù)或者增添客戶需要的額外的新服務(wù)洲劣。為了保證客戶端使用的透明性备蚓,所訪問的真實對象與代理對象需要實現(xiàn)相同的接口。
代理模式的結(jié)構(gòu)比較簡單囱稽,其核心是代理類郊尝,為了讓客戶端能夠一致性地對待真實對象和代理對象,在代理模式中引入了抽象層战惊,代理模式結(jié)構(gòu)如下圖所示:
代理模式包含如下三個角色:
(1) Subject(抽象主題角色):它聲明了真實主題和代理主題的共同接口流昏,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程吞获。
(2) Proxy(代理主題角色):它包含了對真實主題的引用况凉,從而可以在任何時候操作真實主題對象;在代理主題角色中提供一個與真實主題角色相同的接口各拷,以便在任何時候都可以替代真實主題刁绒;代理主題角色還可以控制對真實主題的使用,負責在需要的時候創(chuàng)建和刪除真實主題對象烤黍,并對真實主題對象的使用加以約束知市。通常,在代理主題角色中速蕊,客戶端在調(diào)用所引用的真實主題操作之前或之后還需要執(zhí)行其他操作嫂丙,而不僅僅是單純調(diào)用真實主題對象中的操作。
(3) RealSubject(真實主題角色):它定義了代理角色所代表的真實對象规哲,在真實主題角色中實現(xiàn)了真實的業(yè)務(wù)操作奢入,客戶端可以通過代理主題角色間接調(diào)用真實主題角色中定義的操作。
在實際開發(fā)過程中,代理類的實現(xiàn)比上述代碼要復(fù)雜很多腥光,代理模式根據(jù)其目的和實現(xiàn)方式不同可分為很多種類关顷,其中常用的幾種代理模式簡要說明如下:
(1) 遠程代理(Remote Proxy):為一個位于不同的地址空間的對象提供一個本地的代理對象,這個不同的地址空間可以是在同一臺主機中武福,也可是在另一臺主機中议双,遠程代理又稱為大使(Ambassador)。
(2) 虛擬代理(Virtual Proxy):如果需要創(chuàng)建一個資源消耗較大的對象捉片,先創(chuàng)建一個消耗相對較小的對象來表示平痰,真實對象只在需要時才會被真正創(chuàng)建。
(3) 保護代理(Protect Proxy):控制對一個對象的訪問伍纫,可以給不同的用戶提供不同級別的使用權(quán)限宗雇。
(4) 緩沖代理(Cache Proxy):為某一個目標操作的結(jié)果提供臨時的存儲空間,以便多個客戶端可以共享這些結(jié)果莹规。
(5) 智能引用代理(Smart Reference Proxy):當一個對象被引用時赔蒲,提供一些額外的操作,例如將對象被調(diào)用的次數(shù)記錄下來等良漱。
在這些常用的代理模式中吭服,有些代理類的設(shè)計非常復(fù)雜哀卫,例如遠程代理類冗疮,它封裝了底層網(wǎng)絡(luò)通信和對遠程對象的調(diào)用托嚣,其實現(xiàn)較為復(fù)雜。
遠程代理
遠程代理(Remote Proxy)是一種常用的代理模式患久,它使得客戶端程序可以訪問在遠程主機上的對象椅寺,遠程主機可能具有更好的計算性能與處理速度,可以快速響應(yīng)并處理客戶端的請求蒋失。遠程代理可以將網(wǎng)絡(luò)的細節(jié)隱藏起來配并,使得客戶端不必考慮網(wǎng)絡(luò)的存在「吒洌客戶端完全可以認為被代理的遠程業(yè)務(wù)對象是在本地而不是在遠程溉旋,而遠程代理對象承擔了大部分的網(wǎng)絡(luò)通信工作,并負責對遠程業(yè)務(wù)方法的調(diào)用嫉髓。
客戶端對象不能直接訪問遠程主機中的業(yè)務(wù)對象观腊,只能采取間接訪問的方式。遠程業(yè)務(wù)對象在本地主機中有一個代理對象算行,該代理對象負責對遠程業(yè)務(wù)對象的訪問和網(wǎng)絡(luò)通信梧油,它對于客戶端對象而言是透明的≈菪希客戶端無須關(guān)心實現(xiàn)具體業(yè)務(wù)的是誰儡陨,只需要按照服務(wù)接口所定義的方式直接與本地主機中的代理對象交互即可。RPC調(diào)用就是典型的遠程代理。
虛擬代理
虛擬代理(Virtual Proxy)也是一種常用的代理模式骗村,對于一些占用系統(tǒng)資源較多或者加載時間較長的對象嫌褪,可以給這些對象提供一個虛擬代理。在真實對象創(chuàng)建成功之前虛擬代理扮演真實對象的替身胚股,而當真實對象創(chuàng)建之后笼痛,虛擬代理將用戶的請求轉(zhuǎn)發(fā)給真實對象。
通常琅拌,在以下兩種情況下可以考慮使用虛擬代理:
(1) 由于對象本身的復(fù)雜性或者網(wǎng)絡(luò)等原因?qū)е乱粋€對象需要較長的加載時間缨伊,此時可以用一個加載時間相對較短的代理對象來代表真實對象。通常在實現(xiàn)時可以結(jié)合多線程技術(shù)进宝,一個線程用于顯示代理對象刻坊,其他線程用于加載真實對象。這種虛擬代理模式可以應(yīng)用在程序啟動的時候党晋,由于創(chuàng)建代理對象在時間和處理復(fù)雜度上要少于創(chuàng)建真實對象谭胚,因此,在程序啟動時隶校,可以用代理對象代替真實對象初始化漏益,大大加速了系統(tǒng)的啟動時間蛹锰。當需要使用真實對象時深胳,再通過代理對象來引用,而此時真實對象可能已經(jīng)成功加載完畢铜犬,可以縮短用戶的等待時間舞终。
(2) 當一個對象的加載十分耗費系統(tǒng)資源的時候,也非常適合使用虛擬代理癣猾。虛擬代理可以讓那些占用大量內(nèi)存或處理起來非常復(fù)雜的對象推遲到使用它們的時候才創(chuàng)建敛劝,而在此之前用一個相對來說占用資源較少的代理對象來代表真實對象,再通過代理對象來引用真實對象纷宇。為了節(jié)省內(nèi)存夸盟,在第一次引用真實對象時再創(chuàng)建對象,并且該對象可被多次重用像捶,在以后每次訪問時需要檢測所需對象是否已經(jīng)被創(chuàng)建上陕,因此在訪問該對象時需要進行存在性檢測,這需要消耗一定的系統(tǒng)時間拓春,但是可以節(jié)省內(nèi)存空間释簿,這是一種用時間換取空間的做法。
無論是以上哪種情況硼莽,虛擬代理都是用一個“虛假”的代理對象來代表真實對象庶溶,通過代理對象來間接引用真實對象,可以在一定程度上提高系統(tǒng)的性能。
代理模式總結(jié)
代理模式和裝飾模式非常類似偏螺,甚至代碼都類似行疏。二者最主要的區(qū)別是:代理模式中,代理類對被代理的對象有控制權(quán)砖茸,決定其執(zhí)行或者不執(zhí)行隘擎。而裝飾模式中,裝飾類對代理對象沒有控制權(quán)凉夯,只能為其增加一層裝飾货葬,以加強被裝飾對象的功能。裝飾器模式關(guān)注于在一個對象上動態(tài)的添加方法劲够,然而代理模式關(guān)注于控制對對象的訪問震桶。當使用代理模式的時候,我們常常在一個代理類中創(chuàng)建一個對象的實例征绎。而當我們使用裝飾器模 式的時候蹲姐,我們通常的做法是將原始對象作為一個參數(shù)傳給裝飾者的構(gòu)造器。
代理模式使用到極致開發(fā)就是AOP人柿, 這是各位采用Spring架構(gòu)開發(fā)必然要使用到的技術(shù)柴墩,它就是使用了代理和反射的技術(shù)。代理模式在Java的開發(fā)中俯拾皆是凫岖, 是大家非常熟悉的模式江咳, 應(yīng)用非常廣泛, 而裝飾模式是一個比較拘謹?shù)哪J剑?在實際應(yīng)用中接觸比較少哥放, 但是也有不少框架項目使用了裝飾模式歼指, 例如在JDK的java.io.*包中就大量使用裝飾模式。
- 模式優(yōu)點
(1) 能夠協(xié)調(diào)調(diào)用者和被調(diào)用者甥雕,在一定程度上降低了系統(tǒng)的耦合度踩身。
(2) 客戶端可以針對抽象主題角色進行編程,增加和更換代理類無須修改源代碼社露,符合開閉原則挟阻,系統(tǒng)具有較好的靈活性和可擴展性。
此外峭弟,不同類型的代理模式也具有獨特的優(yōu)點附鸽,例如:
(1) 遠程代理為位于兩個不同地址空間對象的訪問提供了一種實現(xiàn)機制,可以將一些消耗資源較多的對象和操作移至性能更好的計算機上孟害,提高系統(tǒng)的整體運行效率拒炎。
(2) 虛擬代理通過一個消耗資源較少的對象來代表一個消耗資源較多的對象,可以在一定程度上節(jié)省系統(tǒng)的運行開銷挨务。
(3) 緩沖代理為某一個操作的結(jié)果提供臨時的緩存存儲空間击你,以便在后續(xù)使用中能夠共享這些結(jié)果玉组,優(yōu)化系統(tǒng)性能,縮短執(zhí)行時間丁侄。
(4) 保護代理可以控制對一個對象的訪問權(quán)限惯雳,為不同用戶提供不同級別的使用權(quán)限。 - 模式缺點
代理模式的主要缺點如下:
(1) 由于在客戶端和真實主題之間增加了代理對象鸿摇,因此有些類型的代理模式可能會造成請求的處理速度變慢石景,例如保護代理。
(2) 實現(xiàn)代理模式需要額外的工作拙吉,而且有些代理模式的實現(xiàn)過程較為復(fù)雜潮孽,例如遠程代理。 - 模式適用場景
代理模式的類型較多筷黔,不同類型的代理模式有不同的優(yōu)缺點往史,它們應(yīng)用于不同的場合:
(1) 當客戶端對象需要訪問遠程主機中的對象時可以使用遠程代理。
(2) 當需要用一個消耗資源較少的對象來代表一個消耗資源較多的對象佛舱,從而降低系統(tǒng)開銷椎例、縮短運行時間時可以使用虛擬代理,例如一個對象需要很長時間才能完成加載時请祖。
(3) 當需要為某一個被頻繁訪問的操作結(jié)果提供一個臨時存儲空間订歪,以供多個客戶端共享訪問這些結(jié)果時可以使用緩沖代理。通過使用緩沖代理肆捕,系統(tǒng)無須在客戶端每一次訪問時都重新執(zhí)行操作刷晋,只需直接從臨時緩沖區(qū)獲取操作結(jié)果即可。
(4) 當需要控制對一個對象的訪問福压,為不同用戶提供不同級別的訪問權(quán)限時可以使用保護代理掏秩。
(5) 當需要為一個對象的訪問(引用)提供一些額外的操作時可以使用智能引用代理或舞。
十一個行為型模式
職責鏈模式-Chain of Responsibility Pattern
職責鏈模式(Chain of Responsibility Pattern):避免請求發(fā)送者與接收者耦合在一起荆姆,讓多個對象都有可能接收請求,將這些對象連接成一條鏈映凳,并且沿著這條鏈傳遞請求胆筒,直到有對象處理它為止。職責鏈模式是一種對象行為型模式诈豌。
職責鏈模式結(jié)構(gòu)的核心在于引入了一個抽象處理者仆救。職責鏈模式結(jié)構(gòu)如下圖所示:
在職責鏈模式結(jié)構(gòu)圖中包含如下幾個角色:
● Handler(抽象處理者):它定義了一個處理請求的接口,一般設(shè)計為抽象類矫渔,由于不同的具體處理者處理請求的方式不同彤蔽,因此在其中定義了抽象請求處理方法。因為每一個處理者的下家還是一個處理者庙洼,因此在抽象處理者中定義了一個抽象處理者類型的對象(如結(jié)構(gòu)圖中的successor)顿痪,作為其對下家的引用镊辕。通過該引用,處理者可以連成一條鏈蚁袭。
● ConcreteHandler(具體處理者):它是抽象處理者的子類征懈,可以處理用戶請求,在具體處理者類中實現(xiàn)了抽象處理者中定義的抽象請求處理方法揩悄,在處理請求之前需要進行判斷卖哎,看是否有相應(yīng)的處理權(quán)限,如果可以處理請求就處理它删性,否則將請求轉(zhuǎn)發(fā)給后繼者亏娜;在具體處理者中可以訪問鏈中下一個對象,以便請求的轉(zhuǎn)發(fā)蹬挺。
在職責鏈模式里照藻,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞汗侵,直到鏈上的某一個對象決定處理此請求幸缕。發(fā)出這個請求的客戶端并不知道鏈上的哪一個對象最終處理這個請求,這使得系統(tǒng)可以在不影響客戶端的情況下動態(tài)地重新組織鏈和分配責任晰韵。
需要注意的是发乔,職責鏈模式并不創(chuàng)建職責鏈,職責鏈的創(chuàng)建工作必須由系統(tǒng)的其他部分來完成雪猪,一般是在使用該職責鏈的客戶端中創(chuàng)建職責鏈栏尚。職責鏈模式降低了請求的發(fā)送端和接收端之間的耦合,使多個對象都有機會處理這個請求只恨。
職責鏈模式總結(jié)
職責鏈模式通過建立一條鏈來組織請求的處理者译仗,請求將沿著鏈進行傳遞,請求發(fā)送者無須知道請求在何時官觅、何處以及如何被處理纵菌,實現(xiàn)了請求發(fā)送者與處理者的解耦。在軟件開發(fā)中休涤,如果遇到有多個對象可以處理同一請求時可以應(yīng)用職責鏈模式咱圆,例如在Web應(yīng)用開發(fā)中創(chuàng)建一個過濾器(Filter)鏈來對請求數(shù)據(jù)進行過濾,在工作流系統(tǒng)中實現(xiàn)公文的分級審批等等功氨,使用職責鏈模式可以較好地解決此類問題序苏。
1.主要優(yōu)點
職責鏈模式的主要優(yōu)點如下:
(1) 職責鏈模式使得一個對象無須知道是其他哪一個對象處理其請求,對象僅需知道該請求會被處理即可捷凄,接收者和發(fā)送者都沒有對方的明確信息忱详,且鏈中的對象不需要知道鏈的結(jié)構(gòu),由客戶端負責鏈的創(chuàng)建跺涤,降低了系統(tǒng)的耦合度匈睁。
(2) 請求處理對象僅需維持一個指向其后繼者的引用管钳,而不需要維持它對所有的候選處理者的引用,可簡化對象的相互連接软舌。
(3) 在給對象分派職責時才漆,職責鏈可以給我們更多的靈活性,可以通過在運行時對該鏈進行動態(tài)的增加或修改來增加或改變處理一個請求的職責佛点。
(4) 在系統(tǒng)中增加一個新的具體請求處理者時無須修改原有系統(tǒng)的代碼醇滥,只需要在客戶端重新建鏈即可,從這一點來看是符合“開閉原則”的超营。
2.主要缺點
職責鏈模式的主要缺點如下:
(1) 由于一個請求沒有明確的接收者鸳玩,那么就不能保證它一定會被處理,該請求可能一直到鏈的末端都得不到處理演闭;一個請求也可能因職責鏈沒有被正確配置而得不到處理不跟。
(2) 對于比較長的職責鏈,請求的處理可能涉及到多個處理對象米碰,系統(tǒng)性能將受到一定影響窝革,而且在進行代碼調(diào)試時不太方便。
(3) 如果建鏈不當吕座,可能會造成循環(huán)調(diào)用虐译,將導(dǎo)致系統(tǒng)陷入死循環(huán)。
3.適用場景
在以下情況下可以考慮使用職責鏈模式:
(1) 有多個對象可以處理同一個請求,具體哪個對象處理該請求待運行時刻再確定,客戶端只需將請求提交到鏈上哥力,而無須關(guān)心請求的處理對象是誰以及它是如何處理的。
(2) 在不明確指定接收者的情況下厢拭,向多個對象中的一個提交一個請求。
(3) 可動態(tài)指定一組對象處理請求撇叁,客戶端可以動態(tài)創(chuàng)建職責鏈來處理請求供鸠,還可以改變鏈中處理者之間的先后次序。
命令模式-Command Pattern
在軟件開發(fā)中税朴,我們經(jīng)常需要向某些對象發(fā)送請求(調(diào)用其中的某個或某些方法)回季,但是并不知道請求的接收者是誰家制,也不知道被請求的操作是哪個正林,此時,我們特別希望能夠以一種松耦合的方式來設(shè)計軟件颤殴,使得請求發(fā)送者與請求接收者能夠消除彼此之間的耦合觅廓,讓對象之間的調(diào)用關(guān)系更加靈活,可以靈活地指定請求接收者以及被請求的操作涵但。命令模式為此類問題提供了一個較為完美的解決方案杈绸。
命令模式可以將請求發(fā)送者和接收者完全解耦帖蔓,發(fā)送者與接收者之間沒有直接引用關(guān)系,發(fā)送請求的對象只需要知道如何發(fā)送請求瞳脓,而不必知道如何完成請求塑娇。
命令模式(Command Pattern):將一個請求封裝為一個對象,從而讓我們可用不同的請求對客戶進行參數(shù)化劫侧;對請求排隊或者記錄請求日志埋酬,以及支持可撤銷的操作。命令模式是一種對象行為型模式烧栋,其別名為動作(Action)模式或事務(wù)(Transaction)模式写妥。
命令模式的定義比較復(fù)雜,提到了很多術(shù)語审姓,例如“用不同的請求對客戶進行參數(shù)化”珍特、“對請求排隊”,“記錄請求日志”魔吐、“支持可撤銷操作”等扎筒,在后面我們將對這些術(shù)語進行一一講解。
命令模式的核心在于引入了命令類酬姆,通過命令類來降低發(fā)送者和接收者的耦合度砸琅,請求發(fā)送者只需指定一個命令對象,再通過命令對象來調(diào)用請求接收者的處理方法轴踱,其結(jié)構(gòu)如下圖所示:
在命令模式結(jié)構(gòu)圖中包含如下幾個角色:
● Command(抽象命令類):抽象命令類一般是一個抽象類或接口症脂,在其中聲明了用于執(zhí)行請求的execute()等方法,通過這些方法可以調(diào)用請求接收者的相關(guān)操作淫僻。
● ConcreteCommand(具體命令類):具體命令類是抽象命令類的子類诱篷,實現(xiàn)了在抽象命令類中聲明的方法,它對應(yīng)具體的接收者對象雳灵,將接收者對象的動作綁定其中棕所。在實現(xiàn)execute()方法時,將調(diào)用接收者對象的相關(guān)操作(Action)悯辙。
● Invoker(調(diào)用者):調(diào)用者即請求發(fā)送者琳省,它通過命令對象來執(zhí)行請求。一個調(diào)用者并不需要在設(shè)計時確定其接收者躲撰,因此它只與抽象命令類之間存在關(guān)聯(lián)關(guān)系针贬。在程序運行時可以將一個具體命令對象注入其中,再調(diào)用具體命令對象的execute()方法拢蛋,從而實現(xiàn)間接調(diào)用請求接收者的相關(guān)操作桦他。
● Receiver(接收者):接收者執(zhí)行與請求相關(guān)的操作,它具體實現(xiàn)對請求的業(yè)務(wù)處理谆棱。
命令模式的本質(zhì)是對請求進行封裝快压,一個請求對應(yīng)于一個命令圆仔,將發(fā)出命令的責任和執(zhí)行命令的責任分割開。每一個命令都是一個操作:請求的一方發(fā)出請求要求執(zhí)行一個操作蔫劣;接收的一方收到請求坪郭,并執(zhí)行相應(yīng)的操作。命令模式允許請求的一方和接收的一方獨立開來脉幢,使得請求的一方不必知道接收請求的一方的接口截粗,更不必知道請求如何被接收、操作是否被執(zhí)行鸵隧、何時被執(zhí)行绸罗,以及是怎么被執(zhí)行的。
命令模式的關(guān)鍵在于引入了抽象命令類豆瘫,請求發(fā)送者針對抽象命令類編程珊蟀,只有實現(xiàn)了抽象命令類的具體命令才與請求接收者相關(guān)聯(lián)。在最簡單的抽象命令類中只包含了一個抽象的execute()方法外驱,每個具體命令類將一個Receiver類型的對象作為一個實例變量進行存儲育灸,從而具體指定一個請求的接收者,不同的具體命令類提供了execute()方法的不同實現(xiàn)昵宇,并調(diào)用不同接收者的請求處理方法磅崭。
宏命令
宏命令(Macro Command)又稱為組合命令,它是組合模式和命令模式聯(lián)用的產(chǎn)物瓦哎。宏命令是一個具體命令類砸喻,它擁有一個集合屬性,在該集合中包含了對其他命令對象的引用蒋譬。通常宏命令不直接與請求接收者交互割岛,而是通過它的成員來調(diào)用接收者的方法。當調(diào)用宏命令的execute()方法時犯助,將遞歸調(diào)用它所包含的每個成員命令的execute()方法癣漆,一個宏命令的成員可以是簡單命令,還可以繼續(xù)是宏命令剂买。執(zhí)行一個宏命令將觸發(fā)多個具體命令的執(zhí)行惠爽,從而實現(xiàn)對命令的批處理,其結(jié)構(gòu)如下圖所示:
命令模式總結(jié)
命令模式是一種使用頻率非常高的設(shè)計模式瞬哼,它可以將請求發(fā)送者與接收者解耦婚肆,請求發(fā)送者通過命令對象來間接引用請求接收者,使得系統(tǒng)具有更好的靈活性和可擴展性倒槐。在基于GUI的軟件開發(fā)旬痹,無論是在電腦桌面應(yīng)用還是在移動應(yīng)用中,命令模式都得到了廣泛的應(yīng)用讨越。
- 主要優(yōu)點
命令模式的主要優(yōu)點如下:
(1) 降低系統(tǒng)的耦合度两残。由于請求者與接收者之間不存在直接引用,因此請求者與接收者之間實現(xiàn)完全解耦把跨,相同的請求者可以對應(yīng)不同的接收者人弓,同樣,相同的接收者也可以供不同的請求者使用着逐,兩者之間具有良好的獨立性崔赌。
(2) 新的命令可以很容易地加入到系統(tǒng)中。由于增加新的具體命令類不會影響到其他類耸别,因此增加新的具體命令類很容易健芭,無須修改原有系統(tǒng)源代碼,甚至客戶類代碼秀姐,滿足“開閉原則”的要求慈迈。
(3) 可以比較容易地設(shè)計一個命令隊列或宏命令(組合命令)。
(4) 為請求的撤銷(Undo)和恢復(fù)(Redo)操作提供了一種設(shè)計和實現(xiàn)方案省有。 - 主要缺點
命令模式的主要缺點如下:
使用命令模式可能會導(dǎo)致某些系統(tǒng)有過多的具體命令類痒留。因為針對每一個對請求接收者的調(diào)用操作都需要設(shè)計一個具體命令類,因此在某些系統(tǒng)中可能需要提供大量的具體命令類蠢沿,這將影響命令模式的使用伸头。 - 適用場景
在以下情況下可以考慮使用命令模式:
(1) 系統(tǒng)需要將請求調(diào)用者和請求接收者解耦,使得調(diào)用者和接收者不直接交互舷蟀。請求調(diào)用者無須知道接收者的存在恤磷,也無須知道接收者是誰,接收者也無須關(guān)心何時被調(diào)用野宜。
(2) 系統(tǒng)需要在不同的時間指定請求碗殷、將請求排隊和執(zhí)行請求。一個命令對象和請求的初始調(diào)用者可以有不同的生命期速缨,換言之锌妻,最初的請求發(fā)出者可能已經(jīng)不在了,而命令對象本身仍然是活動的旬牲,可以通過該命令對象去調(diào)用請求接收者仿粹,而無須關(guān)心請求調(diào)用者的存在性,可以通過請求日志文件等機制來具體實現(xiàn)原茅。
(3) 系統(tǒng)需要支持命令的撤銷(Undo)操作和恢復(fù)(Redo)操作吭历。
(4) 系統(tǒng)需要將一組操作組合在一起形成宏命令。
解釋器模式-Interpreter Pattern
文法規(guī)則和抽象語法樹
解釋器模式描述了如何為簡單的語言定義一個文法擂橘,如何在該語言中表示一個句子晌区,以及如何解釋這些句子。在正式分析解釋器模式結(jié)構(gòu)之前,我們先來學(xué)習(xí)如何表示一個語言的文法規(guī)則以及如何構(gòu)造一棵抽象語法樹朗若。例如“1 + 2 + 3 – 4 + 1”恼五,可以使用如下文法規(guī)則來定義:
expression ::= value | operation
operation ::= expression '+' expression | expression '-' expression
value ::= an integer //一個整數(shù)值
該文法規(guī)則包含三條語句,第一條表示表達式的組成方式哭懈,其中value和operation是后面兩個語言單位的定義灾馒,每一條語句所定義的字符串如operation和value稱為語言構(gòu)造成分或語言單位,符號“::=”表示“定義為”的意思遣总,其左邊的語言單位通過右邊來進行說明和定義睬罗,語言單位對應(yīng)終結(jié)符表達式和非終結(jié)符表達式。如本規(guī)則中的operation是非終結(jié)符表達式旭斥,它的組成元素仍然可以是表達式容达,可以進一步分解,而value是終結(jié)符表達式垂券,它的組成元素是最基本的語言單位花盐,不能再進行分解。
在文法規(guī)則定義中可以使用一些符號來表示不同的含義圆米,如使用“|”表示或卒暂,使用“{”和“}”表示組合,使用“*”表示出現(xiàn)0次或多次等娄帖,其中使用頻率最高的符號是表示“或”關(guān)系的“|”也祠,如文法規(guī)則“boolValue ::= 0 | 1”表示終結(jié)符表達式boolValue的取值可以為0或者1。
除了使用文法規(guī)則來定義一個語言近速,在解釋器模式中還可以通過一種稱之為抽象語法樹(Abstract Syntax Tree, AST)的圖形方式來直觀地表示語言的構(gòu)成诈嘿,每一棵抽象語法樹對應(yīng)一個語言實例,如加法/減法表達式語言中的語句“1+ 2 + 3 – 4 + 1”削葱,可以通過如下圖所示抽象語法樹來表示:
在該抽象語法樹中奖亚,可以通過終結(jié)符表達式value和非終結(jié)符表達式operation組成復(fù)雜的語句,每個文法規(guī)則的語言實例都可以表示為一個抽象語法樹析砸,即每一條具體的語句都可以用類似上圖所示的抽象語法樹來表示昔字,在圖中終結(jié)符表達式類的實例作為樹的葉子節(jié)點,而非終結(jié)符表達式類的實例作為非葉子節(jié)點首繁,它們可以將終結(jié)符表達式類的實例以及包含終結(jié)符和非終結(jié)符實例的子表達式作為其子節(jié)點作郭。抽象語法樹描述了如何構(gòu)成一個復(fù)雜的句子,通過對抽象語法樹的分析弦疮,可以識別出語言中的終結(jié)符類和非終結(jié)符類夹攒。
解釋器模式
解釋器模式是一種使用頻率相對較低但學(xué)習(xí)難度較大的設(shè)計模式,它用于描述如何使用面向?qū)ο笳Z言構(gòu)成一個簡單的語言解釋器胁塞。在某些情況下咏尝,為了更好地描述某一些特定類型的問題压语,我們可以創(chuàng)建一種新的語言,這種語言擁有自己的表達式和結(jié)構(gòu)编检,即文法規(guī)則胎食,這些問題的實例將對應(yīng)為該語言中的句子。此時蒙谓,可以使用解釋器模式來設(shè)計這種新的語言斥季。對解釋器模式的學(xué)習(xí)能夠加深我們對面向?qū)ο笏枷氲睦斫庋低埃⑶艺莆站幊陶Z言中文法規(guī)則的解釋過程累驮。
解釋器模式(Interpreter Pattern):定義一個語言的文法,并且建立一個解釋器來解釋該語言中的句子舵揭,這里的“語言”是指使用規(guī)定格式和語法的代碼谤专。解釋器模式是一種類行為型模式。
由于表達式可分為終結(jié)符表達式和非終結(jié)符表達式午绳,因此解釋器模式的結(jié)構(gòu)與組合模式的結(jié)構(gòu)有些類似置侍,但在解釋器模式中包含更多的組成元素,它的結(jié)構(gòu)如下圖所示:
在解釋器模式結(jié)構(gòu)圖中包含如下幾個角色:
● AbstractExpression(抽象表達式):在抽象表達式中聲明了抽象的解釋操作拦焚,它是所有終結(jié)符表達式和非終結(jié)符表達式的公共父類蜡坊。
● TerminalExpression(終結(jié)符表達式):終結(jié)符表達式是抽象表達式的子類,它實現(xiàn)了與文法中的終結(jié)符相關(guān)聯(lián)的解釋操作赎败,在句子中的每一個終結(jié)符都是該類的一個實例秕衙。通常在一個解釋器模式中只有少數(shù)幾個終結(jié)符表達式類,它們的實例可以通過非終結(jié)符表達式組成較為復(fù)雜的句子僵刮。
● NonterminalExpression(非終結(jié)符表達式):非終結(jié)符表達式也是抽象表達式的子類据忘,它實現(xiàn)了文法中非終結(jié)符的解釋操作,由于在非終結(jié)符表達式中可以包含終結(jié)符表達式搞糕,也可以繼續(xù)包含非終結(jié)符表達式勇吊,因此其解釋操作一般通過遞歸的方式來完成。
● Context(環(huán)境類):環(huán)境類又稱為上下文類窍仰,它用于存儲解釋器之外的一些全局信息汉规,通常它臨時存儲了需要解釋的語句。
最后還需要一個工具類用于對輸入指令進行處理驹吮,將輸入指令按照優(yōu)先級語法解析成符號表達式存入棧中针史,再從棧中取出按約定語法構(gòu)成的能夠解析的符號表達式气忠,逐個解析谬莹。
解釋器模式總結(jié)
解釋器模式為自定義語言的設(shè)計和實現(xiàn)提供了一種解決方案筒狠,它用于定義一組文法規(guī)則并通過這組文法規(guī)則來解釋語言中的句子拱绑。雖然解釋器模式的使用頻率不是特別高茎活,但是它在正則表達式询刹、XML文檔解釋等領(lǐng)域還是得到了廣泛使用虫腋。與解釋器模式類似田晚,目前還誕生了很多基于抽象語法樹的源代碼處理工具,例如Mysql的語法解析工具druid等智润,它可以用于表示Java語言的語法結(jié)構(gòu)及舍,用戶可以通過擴展其功能,創(chuàng)建自己的文法規(guī)則窟绷。
- 主要優(yōu)點
解釋器模式的主要優(yōu)點如下:
(1) 易于改變和擴展文法锯玛。由于在解釋器模式中使用類來表示語言的文法規(guī)則,因此可以通過繼承等機制來改變或擴展文法兼蜈。
(2) 每一條文法規(guī)則都可以表示為一個類攘残,因此可以方便地實現(xiàn)一個簡單的語言。
(3) 實現(xiàn)文法較為容易为狸。在抽象語法樹中每一個表達式節(jié)點類的實現(xiàn)方式都是相似的歼郭,這些類的代碼編寫都不會特別復(fù)雜,還可以通過一些工具自動生成節(jié)點類代碼辐棒。
(4) 增加新的解釋表達式較為方便病曾。如果用戶需要增加新的解釋表達式只需要對應(yīng)增加一個新的終結(jié)符表達式或非終結(jié)符表達式類,原有表達式類代碼無須修改漾根,符合“開閉原則”泰涂。 - 主要缺點
解釋器模式的主要缺點如下:
(1) 對于復(fù)雜文法難以維護。在解釋器模式中辐怕,每一條規(guī)則至少需要定義一個類逼蒙,因此如果一個語言包含太多文法規(guī)則,類的個數(shù)將會急劇增加秘蛇,導(dǎo)致系統(tǒng)難以管理和維護其做,此時可以考慮使用語法分析程序等方式來取代解釋器模式。
(2) 執(zhí)行效率較低赁还。由于在解釋器模式中使用了大量的循環(huán)和遞歸調(diào)用妖泄,因此在解釋較為復(fù)雜的句子時其速度很慢,而且代碼的調(diào)試過程也比較麻煩艘策。 - 適用場景
在以下情況下可以考慮使用解釋器模式:
(1) 可以將一個需要解釋執(zhí)行的語言中的句子表示為一個抽象語法樹蹈胡。
(2) 一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進行表達。
(3) 一個語言的文法較為簡單朋蔫。
(4) 執(zhí)行效率不是關(guān)鍵問題罚渐。【注:高效的解釋器通常不是通過直接解釋抽象語法樹來實現(xiàn)的驯妄,而是需要將它們轉(zhuǎn)換成其他形式荷并,使用解釋器模式的執(zhí)行效率并不高∏嗳樱】
迭代器模式-Iterator Pattern
在軟件開發(fā)中源织,我們經(jīng)常需要使用聚合對象來存儲一系列數(shù)據(jù)翩伪。聚合對象擁有兩個職責:一是存儲數(shù)據(jù);二是遍歷數(shù)據(jù)谈息。從依賴性來看缘屹,前者是聚合對象的基本職責;而后者既是可變化的侠仇,又是可分離的轻姿。因此,可以將遍歷數(shù)據(jù)的行為從聚合對象中分離出來逻炊,封裝在一個被稱之為“迭代器”的對象中互亮,由迭代器來提供遍歷聚合對象內(nèi)部數(shù)據(jù)的行為,這將簡化聚合對象的設(shè)計嗅骄,更符合“單一職責原則”的要求胳挎。
迭代器模式(Iterator Pattern):提供一種方法來訪問聚合對象饼疙,而不用暴露這個對象的內(nèi)部表示溺森,其別名為游標(Cursor)。迭代器模式是一種對象行為型模式窑眯。
在迭代器模式結(jié)構(gòu)中包含聚合和迭代器兩個層次結(jié)構(gòu)屏积,考慮到系統(tǒng)的靈活性和可擴展性,在迭代器模式中應(yīng)用了工廠方法模式磅甩,其模式結(jié)構(gòu)如下圖所示:
在迭代器模式結(jié)構(gòu)圖中包含如下幾個角色:
● Iterator(抽象迭代器):它定義了訪問和遍歷元素的接口炊林,聲明了用于遍歷數(shù)據(jù)元素的方法,例如:用于獲取第一個元素的first()方法卷要,用于訪問下一個元素的next()方法渣聚,用于判斷是否還有下一個元素的hasNext()方法,用于獲取當前元素的currentItem()方法等僧叉,在具體迭代器中將實現(xiàn)這些方法奕枝。
● ConcreteIterator(具體迭代器):它實現(xiàn)了抽象迭代器接口,完成對聚合對象的遍歷瓶堕,同時在具體迭代器中通過游標來記錄在聚合對象中所處的當前位置隘道,在具體實現(xiàn)時,游標通常是一個表示位置的非負整數(shù)郎笆。
● Aggregate(抽象聚合類):它用于存儲和管理元素對象谭梗,聲明一個createIterator()方法用于創(chuàng)建一個迭代器對象,充當抽象迭代器工廠角色宛蚓。
● ConcreteAggregate(具體聚合類):它實現(xiàn)了在抽象聚合類中聲明的createIterator()方法激捏,該方法返回一個與該具體聚合類對應(yīng)的具體迭代器ConcreteIterator實例。
在迭代器模式中凄吏,提供了一個外部的迭代器來對聚合對象進行訪問和遍歷远舅,迭代器定義了一個訪問該聚合元素的接口壹置,并且可以跟蹤當前遍歷的元素,了解哪些元素已經(jīng)遍歷過而哪些沒有表谊。迭代器的引入钞护,將使得對一個復(fù)雜聚合對象的操作變得簡單。
需要注意的是抽象迭代器接口的設(shè)計非常重要爆办,一方面需要充分滿足各種遍歷操作的要求难咕,盡量為各種遍歷方法都提供聲明,另一方面又不能包含太多方法距辆,接口中方法太多將給子類的實現(xiàn)帶來麻煩余佃。因此,可以考慮使用抽象類來設(shè)計抽象迭代器跨算,在抽象類中為每一個方法提供一個空的默認實現(xiàn)爆土。如果需要在具體迭代器中為聚合對象增加全新的遍歷操作,則必須修改抽象迭代器和具體迭代器的源代碼诸蚕,這將違反“開閉原則”步势,因此在設(shè)計時要考慮全面,避免之后修改接口背犯。
在迭代器模式結(jié)構(gòu)圖中坏瘩,我們可以看到具體迭代器類和具體聚合類之間存在雙重關(guān)系,其中一個關(guān)系為關(guān)聯(lián)關(guān)系漠魏,在具體迭代器中需要維持一個對具體聚合對象的引用倔矾,該關(guān)聯(lián)關(guān)系的目的是訪問存儲在聚合對象中的數(shù)據(jù),以便迭代器能夠?qū)@些數(shù)據(jù)進行遍歷操作柱锹。
使用內(nèi)部類實現(xiàn)迭代器
除了使用關(guān)聯(lián)關(guān)系外哪自,為了能夠讓迭代器可以訪問到聚合對象中的數(shù)據(jù),我們還可以將迭代器類設(shè)計為聚合類的內(nèi)部類禁熏,JDK中的迭代器類就是通過這種方法來實現(xiàn)的壤巷,如下AbstractList類代碼片段所示:
package java.util;
……
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
......
private class Itr implements Iterator<E> {
int cursor = 0;
......
}
……
}
無論使用哪種實現(xiàn)機制,客戶端代碼都是一樣的匹层,也就是說客戶端無須關(guān)心具體迭代器對象的創(chuàng)建細節(jié)隙笆,只需通過調(diào)用工廠方法createIterator()即可得到一個可用的迭代器對象,這也是使用工廠方法模式的好處升筏,通過工廠來封裝對象的創(chuàng)建過程撑柔,簡化了客戶端的調(diào)用。
JDK內(nèi)置迭代器
為了讓開發(fā)人員能夠更加方便地操作聚合對象您访,在Java铅忿、C#等編程語言中都提供了內(nèi)置迭代器。在Java集合框架中灵汪,常用的List和Set等聚合類都繼承(或?qū)崿F(xiàn))了java.util.Collection接口檀训,在Collection接口中聲明了如下方法(部分):
package java.util;
public interface Collection<E> extends Iterable<E> {
……
boolean add(Object c);
boolean addAll(Collection c);
boolean remove(Object o);
boolean removeAll(Collection c);
boolean remainAll(Collection c);
Iterator iterator();
……
}
除了包含一些增加元素和刪除元素的方法外柑潦,還提供了一個iterator()方法,用于返回一個Iterator迭代器對象峻凫,以便遍歷聚合中的元素渗鬼;具體的Java聚合類可以通過實現(xiàn)該iterator()方法返回一個具體的Iterator對象。
JDK中定義了抽象迭代器接口Iterator荧琼,代碼如下所示:
package java.util;
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
其中譬胎,hasNext()用于判斷聚合對象中是否還存在下一個元素,為了不拋出異常命锄,在每次調(diào)用next()之前需先調(diào)用hasNext()堰乔,如果有可供訪問的元素,則返回true脐恩;next()方法用于將游標移至下一個元素镐侯,通過它可以逐個訪問聚合中的元素,它返回游標所越過的那個元素的引用驶冒;remove()方法用于刪除上次調(diào)用next()時所返回的元素苟翻。
在JDK中,Collection接口和Iterator接口充當了迭代器模式的抽象層只怎,分別對應(yīng)于抽象聚合類和抽象迭代器袜瞬,而Collection接口的子類充當了具體聚合類,下面以List為例加以說明身堡,圖6列出了JDK中部分與List有關(guān)的類及它們之間的關(guān)系:
迭代器模式總結(jié)
迭代器模式是一種使用頻率非常高的設(shè)計模式,通過引入迭代器可以將數(shù)據(jù)的遍歷功能從聚合對象中分離出來拍鲤,聚合對象只負責存儲數(shù)據(jù)贴谎,而遍歷數(shù)據(jù)由迭代器來完成。由于很多編程語言的類庫都已經(jīng)實現(xiàn)了迭代器模式季稳,因此在實際開發(fā)中擅这,我們只需要直接使用Java、C#等語言已定義好的迭代器即可景鼠,迭代器已經(jīng)成為我們操作聚合對象的基本工具之一仲翎。
- 主要優(yōu)點
迭代器模式的主要優(yōu)點如下:
(1) 它支持以不同的方式遍歷一個聚合對象,在同一個聚合對象上可以定義多種遍歷方式铛漓。在迭代器模式中只需要用一個不同的迭代器來替換原有迭代器即可改變遍歷算法溯香,我們也可以自己定義迭代器的子類以支持新的遍歷方式。
(2) 迭代器簡化了聚合類浓恶。由于引入了迭代器玫坛,在原有的聚合對象中不需要再自行提供數(shù)據(jù)遍歷等方法,這樣可以簡化聚合類的設(shè)計包晰。
(3) 在迭代器模式中湿镀,由于引入了抽象層炕吸,增加新的聚合類和迭代器類都很方便,無須修改原有代碼勉痴,滿足“開閉原則”的要求赫模。 - 主要缺點
迭代器模式的主要缺點如下:
(1) 由于迭代器模式將存儲數(shù)據(jù)和遍歷數(shù)據(jù)的職責分離,增加新的聚合類需要對應(yīng)增加新的迭代器類蒸矛,類的個數(shù)成對增加嘴瓤,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
(2) 抽象迭代器的設(shè)計難度較大莉钙,需要充分考慮到系統(tǒng)將來的擴展廓脆,例如JDK內(nèi)置迭代器Iterator就無法實現(xiàn)逆向遍歷,如果需要實現(xiàn)逆向遍歷磁玉,只能通過其子類ListIterator等來實現(xiàn)停忿,而ListIterator迭代器無法用于操作Set類型的聚合對象。在自定義迭代器時蚊伞,創(chuàng)建一個考慮全面的抽象迭代器并不是件很容易的事情席赂。 - 適用場景
在以下情況下可以考慮使用迭代器模式:
(1) 訪問一個聚合對象的內(nèi)容而無須暴露它的內(nèi)部表示。將聚合對象的訪問與內(nèi)部數(shù)據(jù)的存儲分離时迫,使得訪問聚合對象時無須了解其內(nèi)部實現(xiàn)細節(jié)颅停。
(2) 需要為一個聚合對象提供多種遍歷方式。
(3) 為遍歷不同的聚合結(jié)構(gòu)提供一個統(tǒng)一的接口掠拳,在該接口的實現(xiàn)類中為不同的聚合結(jié)構(gòu)提供不同的遍歷方式癞揉,而客戶端可以一致性地操作該接口。
中介者模式-Mediator Pattern
如果在一個系統(tǒng)中對象之間的聯(lián)系呈現(xiàn)為網(wǎng)狀結(jié)構(gòu)溺欧。對象之間存在大量的多對多聯(lián)系喊熟,將導(dǎo)致系統(tǒng)非常復(fù)雜,這些對象既會影響別的對象姐刁,也會被別的對象所影響芥牌,這些對象稱為同事對象,它們之間通過彼此的相互作用實現(xiàn)系統(tǒng)的行為聂使。在網(wǎng)狀結(jié)構(gòu)中壁拉,幾乎每個對象都需要與其他對象發(fā)生相互作用,而這種相互作用表現(xiàn)為一個對象與另外一個對象的直接耦合柏靶,這將導(dǎo)致一個過度耦合的系統(tǒng)弃理。
中介者模式可以使對象之間的關(guān)系數(shù)量急劇減少,通過引入中介者對象宿礁,可以將系統(tǒng)的網(wǎng)狀結(jié)構(gòu)變成以中介者為中心的星形結(jié)構(gòu)案铺。在這個星形結(jié)構(gòu)中,同事對象不再直接與另一個對象聯(lián)系,它通過中介者對象與另一個對象發(fā)生相互作用控汉。中介者對象的存在保證了對象結(jié)構(gòu)上的穩(wěn)定笔诵,也就是說,系統(tǒng)的結(jié)構(gòu)不會因為新對象的引入帶來大量的修改工作姑子。
如果在一個系統(tǒng)中對象之間存在多對多的相互關(guān)系乎婿,我們可以將對象之間的一些交互行為從各個對象中分離出來,并集中封裝在一個中介者對象中街佑,并由該中介者進行統(tǒng)一協(xié)調(diào)谢翎,這樣對象之間多對多的復(fù)雜關(guān)系就轉(zhuǎn)化為相對簡單的一對多關(guān)系。通過引入中介者來簡化對象之間的復(fù)雜交互沐旨,中介者模式是“迪米特法則”的一個典型應(yīng)用森逮。
中介者模式(Mediator Pattern):用一個中介對象(中介者)來封裝一系列的對象交互,中介者使各對象不需要顯式地相互引用磁携,從而使其耦合松散吮播,而且可以獨立地改變它們之間的交互括授。中介者模式又稱為調(diào)停者模式匾二,它是一種對象行為型模式豆励。
在中介者模式中,我們引入了用于協(xié)調(diào)其他對象/類之間相互調(diào)用的中介者類统诺,為了讓系統(tǒng)具有更好的靈活性和可擴展性歪脏,通常還提供了抽象中介者,其結(jié)構(gòu)圖如下圖所示:
在中介者模式結(jié)構(gòu)圖中包含如下幾個角色:
● Mediator(抽象中介者):它定義一個接口粮呢,該接口用于與各同事對象之間進行通信婿失。
● ConcreteMediator(具體中介者):它是抽象中介者的子類,通過協(xié)調(diào)各個同事對象來實現(xiàn)協(xié)作行為鬼贱,它維持了對各個同事對象的引用移怯。
● Colleague(抽象同事類):它定義各個同事類公有的方法,并聲明了一些抽象方法來供子類實現(xiàn)这难,同時它維持了一個對抽象中介者類的引用,其子類可以通過該引用來與中介者通信葡秒。
● ConcreteColleague(具體同事類):它是抽象同事類的子類姻乓;每一個同事對象在需要和其他同事對象通信時,先與中介者通信眯牧,通過中介者來間接完成與其他同事類的通信蹋岩;在具體同事類中實現(xiàn)了在抽象同事類中聲明的抽象方法。
中介者模式的核心在于中介者類的引入学少,在中介者模式中剪个,中介者類承擔了兩方面的職責:
(1) 中轉(zhuǎn)作用(結(jié)構(gòu)性):通過中介者提供的中轉(zhuǎn)作用,各個同事對象就不再需要顯式引用其他同事版确,當需要和其他同事進行通信時扣囊,可通過中介者來實現(xiàn)間接調(diào)用乎折。該中轉(zhuǎn)作用屬于中介者在結(jié)構(gòu)上的支持。
(2) 協(xié)調(diào)作用(行為性):中介者可以更進一步的對同事之間的關(guān)系進行封裝侵歇,同事可以一致的和中介者進行交互骂澄,而不需要指明中介者需要具體怎么做,中介者根據(jù)封裝在自身內(nèi)部的協(xié)調(diào)邏輯惕虑,對同事的請求進行進一步處理坟冲,將同事成員之間的關(guān)系行為進行分離和封裝。該協(xié)調(diào)作用屬于中介者在行為上的支持溃蔫。
在具體同事類ConcreteColleague中實現(xiàn)了在抽象同事類中聲明的方法健提,其中部分方法是同事類的自身方法(Self-Method),用于處理自己的行為伟叛,而另外部分方式是依賴方法(Depend-Method)私痹,用于調(diào)用在中介者中定義的方法,依賴中介者來完成相應(yīng)的行為痪伦,例如調(diào)用另一個同事類的相關(guān)方法侄榴。
在中介者模式的實際使用過程中,如果需要引入新的具體同事類网沾,只需要繼承抽象同事類并實現(xiàn)其中的方法即可癞蚕,由于具體同事類之間并無直接的引用關(guān)系,因此原有所有同事類無須進行任何修改辉哥,它們與新增同事對象之間的交互可以通過修改或者增加具體中介者類來實現(xiàn)桦山;如果需要在原有系統(tǒng)中增加新的具體中介者類,只需要繼承抽象中介者類(或已有的具體中介者類)并覆蓋其中定義的方法即可醋旦,在新的具體中介者中可以通過不同的方式來處理對象之間的交互恒水,也可以增加對新增同事的引用和調(diào)用。在客戶端中只需要修改少許代碼(如果引入配置文件的話有時可以不修改任何代碼)就可以實現(xiàn)中介者的更換饲齐。
中介者模式總結(jié)
中介者模式將一個網(wǎng)狀的系統(tǒng)結(jié)構(gòu)變成一個以中介者對象為中心的星形結(jié)構(gòu)钉凌,在這個星型結(jié)構(gòu)中,使用中介者對象與其他對象的一對多關(guān)系來取代原有對象之間的多對多關(guān)系捂人。中介者模式在事件驅(qū)動類軟件中應(yīng)用較為廣泛御雕,特別是基于GUI(Graphical User Interface,圖形用戶界面)的應(yīng)用軟件滥搭,此外酸纲,在類與類之間存在錯綜復(fù)雜的關(guān)聯(lián)關(guān)系的系統(tǒng)中,中介者模式都能得到較好的應(yīng)用瑟匆。
- 主要優(yōu)點
中介者模式的主要優(yōu)點如下:
(1) 中介者模式簡化了對象之間的交互闽坡,它用中介者和同事的一對多交互代替了原來同事之間的多對多交互,一對多關(guān)系更容易理解、維護和擴展疾嗅,將原本難以理解的網(wǎng)狀結(jié)構(gòu)轉(zhuǎn)換成相對簡單的星型結(jié)構(gòu)外厂。
(2) 中介者模式可將各同事對象解耦。中介者有利于各同事之間的松耦合宪迟,我們可以獨立的改變和復(fù)用每一個同事和中介者酣衷,增加新的中介者和新的同事類都比較方便,更好地符合“開閉原則”次泽。
(3) 可以減少子類生成穿仪,中介者將原本分布于多個對象間的行為集中在一起,改變這些行為只需生成新的中介者子類即可意荤,這使各個同事類可被重用啊片,無須對同事類進行擴展。 - 主要缺點
中介者模式的主要缺點如下:
在具體中介者類中包含了大量同事之間的交互細節(jié)玖像,可能會導(dǎo)致具體中介者類非常復(fù)雜紫谷,使得系統(tǒng)難以維護。 - 適用場景
在以下情況下可以考慮使用中介者模式:
(1) 系統(tǒng)中對象之間存在復(fù)雜的引用關(guān)系捐寥,系統(tǒng)結(jié)構(gòu)混亂且難以理解笤昨。
(2) 一個對象由于引用了其他很多對象并且直接和這些對象通信,導(dǎo)致難以復(fù)用該對象握恳。
(3) 想通過一個中間類來封裝多個類中的行為瞒窒,而又不想生成太多的子類∠缤荩可以通過引入中介者類來實現(xiàn)崇裁,在中介者中定義對象交互的公共行為,如果需要改變行為則可以增加新的具體中介者類束昵。
備忘錄模式-Memento Pattern
備忘錄模式提供了一種狀態(tài)恢復(fù)的實現(xiàn)機制拔稳,使得用戶可以方便地回到一個特定的歷史步驟,當新的狀態(tài)無效或者存在問題時锹雏,可以使用暫時存儲起來的備忘錄將狀態(tài)復(fù)原巴比,當前很多軟件都提供了撤銷(Undo)操作,其中就使用了備忘錄模式礁遵。
備忘錄模式(Memento Pattern):在不破壞封裝的前提下匿辩,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)榛丢,這樣可以在以后將對象恢復(fù)到原先保存的狀態(tài)。它是一種對象行為型模式挺庞,其別名為Token晰赞。
備忘錄模式的核心是備忘錄類以及用于管理備忘錄的負責人類的設(shè)計,其結(jié)構(gòu)如下圖所示:
在備忘錄模式結(jié)構(gòu)圖中包含如下幾個角色:
● Originator(原發(fā)器):它是一個普通類,可以創(chuàng)建一個備忘錄掖鱼,并存儲它的當前內(nèi)部狀態(tài)然走,也可以使用備忘錄來恢復(fù)其內(nèi)部狀態(tài),一般將需要保存內(nèi)部狀態(tài)的類設(shè)計為原發(fā)器戏挡。
●Memento(備忘錄):存儲原發(fā)器的內(nèi)部狀態(tài)芍瑞,根據(jù)原發(fā)器來決定保存哪些內(nèi)部狀態(tài)。備忘錄的設(shè)計一般可以參考原發(fā)器的設(shè)計褐墅,根據(jù)實際需要確定備忘錄類中的屬性拆檬。需要注意的是,除了原發(fā)器本身與負責人類之外妥凳,備忘錄對象不能直接供其他類使用竟贯,原發(fā)器的設(shè)計在不同的編程語言中實現(xiàn)機制會有所不同。
●Caretaker(負責人):負責人又稱為管理者逝钥,它負責保存?zhèn)渫浶寄牵遣荒軐渫浀膬?nèi)容進行操作或檢查。在負責人類中可以存儲一個或多個備忘錄對象艘款,它只負責存儲對象持际,而不能修改對象,也無須知道對象的實現(xiàn)細節(jié)哗咆。
理解備忘錄模式并不難蜘欲,但關(guān)鍵在于如何設(shè)計備忘錄類和負責人類。由于在備忘錄中存儲的是原發(fā)器的中間狀態(tài)岳枷,因此需要防止原發(fā)器以外的其他對象訪問備忘錄芒填,特別是不允許其他對象來修改備忘錄。
對于備忘錄類Memento而言空繁,它通常提供了與原發(fā)器相對應(yīng)的屬性(可以是全部殿衰,也可以是部分)用于存儲原發(fā)器的狀態(tài)。
在設(shè)計備忘錄類時需要考慮其封裝性盛泡,除了Originator類闷祥,不允許其他類來調(diào)用備忘錄類Memento的構(gòu)造函數(shù)與相關(guān)方法,如果不考慮封裝性傲诵,允許其他類調(diào)用setState()等方法凯砍,將導(dǎo)致在備忘錄中保存的歷史狀態(tài)發(fā)生改變,通過撤銷操作所恢復(fù)的狀態(tài)就不再是真實的歷史狀態(tài)拴竹,備忘錄模式也就失去了本身的意義悟衩。
在使用Java語言實現(xiàn)備忘錄模式時,一般通過將Memento類與Originator類定義在同一個包(package)中來實現(xiàn)封裝栓拜,在Java語言中可使用默認訪問標識符來定義Memento類座泳,即保證其包內(nèi)可見惠昔。只有Originator類可以對Memento進行訪問,而限制了其他類對Memento的訪問挑势。在 Memento中保存了Originator的state值镇防,如果Originator中的state值改變之后需撤銷,可以通過調(diào)用它的restoreMemento()方法進行恢復(fù)潮饱。
對于負責人類Caretaker来氧,它用于保存?zhèn)渫泴ο螅⑻峁ゞetMemento()方法用于向客戶端返回一個備忘錄對象香拉,原發(fā)器通過使用這個備忘錄對象可以回到某個歷史狀態(tài)啦扬。在Caretaker類中不應(yīng)該直接調(diào)用Memento中的狀態(tài)改變方法,它的作用僅僅用于存儲備忘錄對象缕溉。將原發(fā)器備份生成的備忘錄對象存儲在其中考传,當用戶需要對原發(fā)器進行恢復(fù)時再將存儲在其中的備忘錄對象取出。
有時候用戶需要撤銷多步操作证鸥。如何實現(xiàn)多次撤銷呢僚楞?那就是在負責人類中定義一個集合來存儲多個備忘錄,每個備忘錄負責保存一個歷史狀態(tài)枉层,在撤銷時可以對備忘錄集合進行逆向遍歷泉褐,回到一個指定的歷史狀態(tài),而且還可以對備忘錄集合進行正向遍歷鸟蜡,實現(xiàn)重做(Redo)操作膜赃,即取消撤銷,讓對象狀態(tài)得到恢復(fù)揉忘。
備忘錄是一個很特殊的對象跳座,只有原發(fā)器對它擁有控制的權(quán)力,負責人只負責管理泣矛,而其他類無法訪問到備忘錄疲眷,因此我們需要對備忘錄進行封裝。
為了實現(xiàn)對備忘錄對象的封裝您朽,需要對備忘錄的調(diào)用進行控制狂丝,對于原發(fā)器而言,它可以調(diào)用備忘錄的所有信息哗总,允許原發(fā)器訪問返回到先前狀態(tài)所需的所有數(shù)據(jù)几颜;對于負責人而言,只負責備忘錄的保存并將備忘錄傳遞給其他對象讯屈;對于其他對象而言蛋哭,只需要從負責人處取出備忘錄對象并將原發(fā)器對象的狀態(tài)恢復(fù),而無須關(guān)心備忘錄的保存細節(jié)涮母。理想的情況是只允許生成該備忘錄的那個原發(fā)器訪問備忘錄的內(nèi)部狀態(tài)具壮。
在實際開發(fā)中准颓,原發(fā)器與備忘錄之間的關(guān)系是非常特殊的,它們要分享信息而不讓其他類知道棺妓,實現(xiàn)的方法因編程語言的不同而有所差異,在C++中可以使用friend關(guān)鍵字炮赦,讓原發(fā)器類和備忘錄類成為友元類怜跑,互相之間可以訪問對象的一些私有的屬性;在Java語言中可以將原發(fā)器類和備忘錄類放在一個包中吠勘,讓它們之間滿足默認的包內(nèi)可見性性芬,也可以將備忘錄類作為原發(fā)器類的內(nèi)部類,使得只有原發(fā)器才可以訪問備忘錄中的數(shù)據(jù)剧防,其他對象都無法使用備忘錄中的數(shù)據(jù)植锉。
備忘錄模式總結(jié)
備忘錄模式在很多軟件的使用過程中普遍存在,但是在應(yīng)用軟件開發(fā)中峭拘,它的使用頻率并不太高俊庇,因為現(xiàn)在很多基于窗體和瀏覽器的應(yīng)用軟件并沒有提供撤銷操作。如果需要為軟件提供撤銷功能鸡挠,備忘錄模式無疑是一種很好的解決方案辉饱。在一些字處理軟件拣展、圖像編輯軟件彭沼、數(shù)據(jù)庫管理系統(tǒng)等軟件中備忘錄模式都得到了很好的應(yīng)用。
1.主要優(yōu)點
備忘錄模式的主要優(yōu)點如下:
(1)它提供了一種狀態(tài)恢復(fù)的實現(xiàn)機制备埃,使得用戶可以方便地回到一個特定的歷史步驟姓惑,當新的狀態(tài)無效或者存在問題時,可以使用暫時存儲起來的備忘錄將狀態(tài)復(fù)原按脚。
(2)備忘錄實現(xiàn)了對信息的封裝于毙,一個備忘錄對象是一種原發(fā)器對象狀態(tài)的表示,不會被其他代碼所改動乘寒。備忘錄保存了原發(fā)器的狀態(tài)望众,采用列表、堆棧等集合來存儲備忘錄對象可以實現(xiàn)多次撤銷操作伞辛。
2.主要缺點
備忘錄模式的主要缺點如下:
資源消耗過大烂翰,如果需要保存的原發(fā)器類的成員變量太多,就不可避免需要占用大量的存儲空間蚤氏,每保存一次對象的狀態(tài)都需要消耗一定的系統(tǒng)資源甘耿。
3.適用場景
在以下情況下可以考慮使用備忘錄模式:
(1)保存一個對象在某一個時刻的全部狀態(tài)或部分狀態(tài),這樣以后需要時它能夠恢復(fù)到先前的狀態(tài)竿滨,實現(xiàn)撤銷操作佳恬。
(2)防止外界對象破壞一個對象歷史狀態(tài)的封裝性捏境,避免將對象歷史狀態(tài)的實現(xiàn)細節(jié)暴露給外界對象。
觀察者模式-Observer Pattern
觀察者模式是使用頻率最高的設(shè)計模式之一毁葱,它用于建立一種對象與對象之間的依賴關(guān)系垫言,一個對象發(fā)生改變時將自動通知其他對象,其他對象將相應(yīng)作出反應(yīng)倾剿。在觀察者模式中筷频,發(fā)生改變的對象稱為觀察目標,而被通知的對象稱為觀察者前痘,一個觀察目標可以對應(yīng)多個觀察者幽勒,而且這些觀察者之間可以沒有任何相互聯(lián)系梦皮,可以根據(jù)需要增加和刪除觀察者岗喉,使得系統(tǒng)更易于擴展哼勇。
觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關(guān)系,使得每當一個對象狀態(tài)發(fā)生改變時最欠,其相關(guān)依賴對象皆得到通知并被自動更新示罗。觀察者模式的別名包括發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式窒所、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式鹉勒。觀察者模式是一種對象行為型模式。
觀察者模式結(jié)構(gòu)中通常包括觀察目標和觀察者兩個繼承層次結(jié)構(gòu)吵取,其結(jié)構(gòu)如下圖所示:
在觀察者模式結(jié)構(gòu)圖中包含如下幾個角色:
● Subject(目標):目標又稱為主題禽额,它是指被觀察的對象。在目標中定義了一個觀察者集合皮官,一個觀察目標可以接受任意數(shù)量的觀察者來觀察脯倒,它提供一系列方法來增加和刪除觀察者對象,同時它定義了通知方法notify()捺氢。目標類可以是接口藻丢,也可以是抽象類或具體類。
● ConcreteSubject(具體目標):具體目標是目標類的子類摄乒,通常它包含有經(jīng)常發(fā)生改變的數(shù)據(jù)悠反,當它的狀態(tài)發(fā)生改變時,向它的各個觀察者發(fā)出通知馍佑;同時它還實現(xiàn)了在目標類中定義的抽象業(yè)務(wù)邏輯方法(如果有的話)斋否。如果無須擴展目標類,則具體目標類可以省略拭荤。
● Observer(觀察者):觀察者將對觀察目標的改變做出反應(yīng)茵臭,觀察者一般定義為接口,該接口聲明了更新數(shù)據(jù)的方法update()舅世,因此又稱為抽象觀察者旦委。
● ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用奇徒,它存儲具體觀察者的有關(guān)狀態(tài),這些狀態(tài)需要和具體目標的狀態(tài)保持一致缨硝;它實現(xiàn)了在抽象觀察者Observer中定義的update()方法摩钙。通常在實現(xiàn)時,可以調(diào)用具體目標類的attach()方法將自己添加到目標類的集合中或通過detach()方法將自己從目標類的集合中刪除追葡。
觀察者模式描述了如何建立對象與對象之間的依賴關(guān)系腺律,以及如何構(gòu)造滿足這種需求的系統(tǒng)。觀察者模式包含觀察目標和觀察者兩類對象宜肉,一個目標可以有任意數(shù)目的與之相依賴的觀察者,一旦觀察目標的狀態(tài)發(fā)生改變翎碑,所有的觀察者都將得到通知谬返。作為對這個通知的響應(yīng),每個觀察者都將監(jiān)視觀察目標的狀態(tài)以使其狀態(tài)與目標狀態(tài)同步日杈,這種交互也稱為發(fā)布-訂閱(Publish-Subscribe)遣铝。觀察目標是通知的發(fā)布者,它發(fā)出通知時并不需要知道誰是它的觀察者莉擒,可以有任意數(shù)目的觀察者訂閱它并接收通知酿炸。
在有些更加復(fù)雜的情況下,具體觀察者類ConcreteObserver的update()方法在執(zhí)行時需要使用到具體目標類ConcreteSubject中的狀態(tài)(屬性)涨冀,因此在ConcreteObserver與ConcreteSubject之間有時候還存在關(guān)聯(lián)或依賴關(guān)系填硕,在ConcreteObserver中定義一個ConcreteSubject實例,通過該實例獲取存儲在ConcreteSubject中的狀態(tài)鹿鳖。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的狀態(tài)屬性扁眯,則可以對觀察者模式的標準結(jié)構(gòu)進行簡化,在具體觀察者ConcreteObserver和具體目標ConcreteSubject之間無須維持對象引用翅帜。如果在具體層具有關(guān)聯(lián)關(guān)系姻檀,系統(tǒng)的擴展性將受到一定的影響,增加新的具體目標類有時候需要修改原有觀察者的代碼涝滴,在一定程度上違反了“開閉原則”绣版,但是如果原有觀察者類無須關(guān)聯(lián)新增的具體目標,則系統(tǒng)擴展性不受影響歼疮。
觀察者模式在Java語言中的地位非常重要杂抽。在JDK的java.util包中,提供了Observable類以及Observer接口腋妙,它們構(gòu)成了JDK對觀察者模式的支持默怨。如下圖所示:
在java.util.Observer接口中只聲明一個方法,它充當抽象觀察者骤素,當觀察目標的狀態(tài)發(fā)生變化時匙睹,該方法將會被調(diào)用愚屁,在Observer的子類中將實現(xiàn)update()方法,即具體觀察者可以根據(jù)需要具有不同的更新行為痕檬。當調(diào)用觀察目標類Observable的notifyObservers()方法時霎槐,將執(zhí)行觀察者類中的update()方法。
java.util.Observable類充當觀察目標類梦谜,在Observable中定義了一個向量Vector來存儲觀察者對象丘跌,它所包含的方法及說明見下表:
方法名 | 方法描述 |
---|---|
Observable() | 構(gòu)造方法,實例化Vector向量唁桩。 |
addObserver(Observer o) | 用于注冊新的觀察者對象到向量中闭树。 |
deleteObserver (Observer o) | 用于刪除向量中的某一個觀察者對象。 |
notifyObservers()和notifyObservers(Object arg) | 通知方法荒澡,用于在方法內(nèi)部循環(huán)調(diào)用向量中每一個觀察者的update()方法报辱。 |
deleteObservers() | 用于清空向量,即刪除向量中所有觀察者對象单山。 |
setChanged() | 該方法被調(diào)用后會設(shè)置一個boolean類型的內(nèi)部標記變量changed的值為true碍现,表示觀察目標對象的狀態(tài)發(fā)生了變化。 |
clearChanged() | 用于將changed變量的值設(shè)為false米奸,表示對象狀態(tài)不再發(fā)生改變或者已經(jīng)通知了所有的觀察者對象昼接,調(diào)用了它們的update()方法。 |
hasChanged() | 用于測試對象狀態(tài)是否改變悴晰。 |
countObservers() | 用于返回向量中觀察者的數(shù)量慢睡。 |
我們可以直接使用Observer接口和Observable類來作為觀察者模式的抽象層,再自定義具體觀察者類和具體觀察目標類膨疏,通過使用JDK中的Observer接口和Observable類一睁,可以更加方便地在Java語言中應(yīng)用觀察者模式。
觀察者模式總結(jié)
觀察者模式是一種使用頻率非常高的設(shè)計模式佃却,無論是移動應(yīng)用者吁、Web應(yīng)用或者桌面應(yīng)用,觀察者模式幾乎無處不在饲帅,它為實現(xiàn)對象之間的聯(lián)動提供了一套完整的解決方案复凳,凡是涉及到一對一或者一對多的對象交互場景都可以使用觀察者模式。觀察者模式廣泛應(yīng)用于各種編程語言的GUI事件處理的實現(xiàn)灶泵,在基于事件的XML解析技術(shù)(如SAX2)以及Web事件處理中也都使用了觀察者模式育八。
1.主要優(yōu)點
觀察者模式的主要優(yōu)點如下:
(1) 觀察者模式可以實現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,定義了穩(wěn)定的消息更新傳遞機制赦邻,并抽象了更新接口髓棋,使得可以有各種各樣不同的表示層充當具體觀察者角色。
(2) 觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。觀察目標只需要維持一個抽象觀察者的集合按声,無須了解其具體觀察者膳犹。由于觀察目標和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次签则。
(3) 觀察者模式支持廣播通信须床,觀察目標會向所有已注冊的觀察者對象發(fā)送通知,簡化了一對多系統(tǒng)設(shè)計的難度渐裂。
(4) 觀察者模式滿足“開閉原則”的要求豺旬,增加新的具體觀察者無須修改原有系統(tǒng)代碼,在具體觀察者與觀察目標之間不存在關(guān)聯(lián)關(guān)系的情況下柒凉,增加新的觀察目標也很方便族阅。
2.主要缺點
觀察者模式的主要缺點如下:
(1) 如果一個觀察目標對象有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間膝捞。
(2) 如果在觀察者和觀察目標之間存在循環(huán)依賴耘分,觀察目標會觸發(fā)它們之間進行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰绑警。
(3) 觀察者模式?jīng)]有相應(yīng)的機制讓觀察者知道所觀察的目標對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標發(fā)生了變化央渣。
3.適用場景
在以下情況下可以考慮使用觀察者模式:
(1) 一個抽象模型有兩個方面计盒,其中一個方面依賴于另一個方面,將這兩個方面封裝在獨立的對象中使它們可以各自獨立地改變和復(fù)用芽丹。
(2) 一個對象的改變將導(dǎo)致一個或多個其他對象也發(fā)生改變北启,而并不知道具體有多少對象將發(fā)生改變,也不知道這些對象是誰拔第。
(3) 需要在系統(tǒng)中創(chuàng)建一個觸發(fā)鏈咕村,A對象的行為將影響B(tài)對象,B對象的行為將影響C對象……蚊俺,可以使用觀察者模式創(chuàng)建一種鏈式觸發(fā)機制懈涛。
狀態(tài)模式-State Pattern
狀態(tài)模式用于解決系統(tǒng)中復(fù)雜對象的狀態(tài)轉(zhuǎn)換以及不同狀態(tài)下行為的封裝問題。當系統(tǒng)中某個對象存在多個狀態(tài)泳猬,這些狀態(tài)之間可以進行轉(zhuǎn)換批钠,而且對象在不同狀態(tài)下行為不相同時可以使用狀態(tài)模式。狀態(tài)模式將一個對象的狀態(tài)從該對象中分離出來得封,封裝到專門的狀態(tài)類中埋心,使得對象狀態(tài)可以靈活變化,對于客戶端而言忙上,無須關(guān)心對象狀態(tài)的轉(zhuǎn)換以及對象所處的當前狀態(tài)拷呆,無論對于何種狀態(tài)的對象,客戶端都可以一致處理。
狀態(tài)模式(State Pattern):允許一個對象在其內(nèi)部狀態(tài)改變時改變它的行為茬斧,對象看起來似乎修改了它的類腰懂。其別名為狀態(tài)對象(Objects for States),狀態(tài)模式是一種對象行為型模式啥供。
在狀態(tài)模式中引入了抽象狀態(tài)類和具體狀態(tài)類悯恍,它們是狀態(tài)模式的核心,其結(jié)構(gòu)如下圖所示:
在狀態(tài)模式結(jié)構(gòu)圖中包含如下幾個角色:
● Context(環(huán)境類):環(huán)境類又稱為上下文類伙狐,它是擁有多種狀態(tài)的對象涮毫。由于環(huán)境類的狀態(tài)存在多樣性且在不同狀態(tài)下對象的行為有所不同,因此將狀態(tài)獨立出去形成單獨的狀態(tài)類贷屎。在環(huán)境類中維護一個抽象狀態(tài)類State的實例罢防,這個實例定義當前狀態(tài),在具體實現(xiàn)時唉侄,它是一個State子類的對象咒吐。
● State(抽象狀態(tài)類):它用于定義一個接口以封裝與環(huán)境類的一個特定狀態(tài)相關(guān)的行為,在抽象狀態(tài)類中聲明了各種不同狀態(tài)對應(yīng)的方法属划,而在其子類中實現(xiàn)類這些方法恬叹,由于不同狀態(tài)下對象的行為可能不同,因此在不同子類中方法的實現(xiàn)可能存在不同同眯,相同的方法可以寫在抽象狀態(tài)類中绽昼。
● ConcreteState(具體狀態(tài)類):它是抽象狀態(tài)類的子類,每一個子類實現(xiàn)一個與環(huán)境類的一個狀態(tài)相關(guān)的行為须蜗,每一個具體狀態(tài)類對應(yīng)環(huán)境的一個具體狀態(tài)硅确,不同的具體狀態(tài)類其行為有所不同。
在狀態(tài)模式的使用過程中明肮,一個對象的狀態(tài)之間還可以進行相互轉(zhuǎn)換菱农,通常有兩種實現(xiàn)狀態(tài)轉(zhuǎn)換的方式:
(1) 統(tǒng)一由環(huán)境類來負責狀態(tài)之間的轉(zhuǎn)換,此時柿估,環(huán)境類還充當了狀態(tài)管理器(State Manager)角色循未,在環(huán)境類的業(yè)務(wù)方法中通過對某些屬性值的判斷實現(xiàn)狀態(tài)轉(zhuǎn)換,還可以提供一個專門的方法用于實現(xiàn)屬性判斷和狀態(tài)轉(zhuǎn)換官份。
(2) 由具體狀態(tài)類來負責狀態(tài)之間的轉(zhuǎn)換只厘,可以在具體狀態(tài)類的業(yè)務(wù)方法中判斷環(huán)境類的某些屬性值再根據(jù)情況為環(huán)境類設(shè)置新的狀態(tài)對象,實現(xiàn)狀態(tài)轉(zhuǎn)換舅巷,同樣羔味,也可以提供一個專門的方法來負責屬性值的判斷和狀態(tài)轉(zhuǎn)換。此時钠右,狀態(tài)類與環(huán)境類之間就將存在依賴或關(guān)聯(lián)關(guān)系赋元,因為狀態(tài)類需要訪問環(huán)境類中的屬性值,這時在狀態(tài)模式結(jié)構(gòu)圖中就有一條State指向Context的組合線。
在有些情況下搁凸,多個環(huán)境對象可能需要共享同一個狀態(tài)媚值,如果希望在系統(tǒng)中實現(xiàn)多個環(huán)境對象共享一個或多個狀態(tài)對象,那么需要將這些狀態(tài)對象定義為環(huán)境類的靜態(tài)成員對象护糖。
狀態(tài)模式總結(jié)
狀態(tài)模式將一個對象在不同狀態(tài)下的不同行為封裝在一個個狀態(tài)類中褥芒,通過設(shè)置不同的狀態(tài)對象可以讓環(huán)境對象擁有不同的行為,而狀態(tài)轉(zhuǎn)換的細節(jié)對于客戶端而言是透明的嫡良,方便了客戶端的使用锰扶。在實際開發(fā)中,狀態(tài)模式具有較高的使用頻率寝受,在工作流和游戲開發(fā)中狀態(tài)模式都得到了廣泛的應(yīng)用坷牛,例如公文狀態(tài)的轉(zhuǎn)換、游戲中角色的升級等很澄。
- 主要優(yōu)點
狀態(tài)模式的主要優(yōu)點如下:
(1) 封裝了狀態(tài)的轉(zhuǎn)換規(guī)則京闰,在狀態(tài)模式中可以將狀態(tài)的轉(zhuǎn)換代碼封裝在環(huán)境類或者具體狀態(tài)類中,可以對狀態(tài)轉(zhuǎn)換代碼進行集中管理甩苛,而不是分散在一個個業(yè)務(wù)方法中蹂楣。
(2) 將所有與某個狀態(tài)有關(guān)的行為放到一個類中,只需要注入一個不同的狀態(tài)對象即可使環(huán)境對象擁有不同的行為讯蒲。
(3) 允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對象合成一體捐迫,而不是提供一個巨大的條件語句塊,狀態(tài)模式可以讓我們避免使用龐大的條件語句來將業(yè)務(wù)方法和狀態(tài)轉(zhuǎn)換代碼交織在一起爱葵。
(4) 可以讓多個環(huán)境對象共享一個狀態(tài)對象,從而減少系統(tǒng)中對象的個數(shù)反浓。 - 主要缺點
狀態(tài)模式的主要缺點如下:
(1) 狀態(tài)模式的使用必然會增加系統(tǒng)中類和對象的個數(shù)萌丈,導(dǎo)致系統(tǒng)運行開銷增大。
(2) 狀態(tài)模式的結(jié)構(gòu)與實現(xiàn)都較為復(fù)雜雷则,如果使用不當將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂辆雾,增加系統(tǒng)設(shè)計的難度。
(3) 狀態(tài)模式對“開閉原則”的支持并不太好月劈,增加新的狀態(tài)類需要修改那些負責狀態(tài)轉(zhuǎn)換的源代碼度迂,否則無法轉(zhuǎn)換到新增狀態(tài);而且修改某個狀態(tài)類的行為也需修改對應(yīng)類的源代碼猜揪。 - 適用場景
在以下情況下可以考慮使用狀態(tài)模式:
(1) 對象的行為依賴于它的狀態(tài)(如某些屬性值)惭墓,狀態(tài)的改變將導(dǎo)致行為的變化。
(2) 在代碼中包含大量與對象狀態(tài)有關(guān)的條件語句而姐,這些條件語句的出現(xiàn)腊凶,會導(dǎo)致代碼的可維護性和靈活性變差,不能方便地增加和刪除狀態(tài),并且導(dǎo)致客戶類與類庫之間的耦合增強钧萍。
策略模式-Strategy Pattern
策略模式(Strategy Pattern):定義一系列算法類褐缠,將每一個算法封裝起來,并讓它們可以相互替換风瘦,策略模式讓算法獨立于使用它的客戶而變化队魏,也稱為政策模式(Policy)。策略模式是一種對象行為型模式万搔。
策略模式的主要目的是將算法的定義與使用分開胡桨,也就是將算法的行為和環(huán)境分開,將算法的定義放在專門的策略類中蟹略,每一個策略類封裝了一種實現(xiàn)算法登失,使用算法的環(huán)境類針對抽象策略類進行編程,符合“依賴倒轉(zhuǎn)原則”挖炬。在出現(xiàn)新的算法時揽浙,只需要增加一個新的實現(xiàn)了抽象策略類的具體策略類即可。策略模式定義如下:
策略模式結(jié)構(gòu)并不復(fù)雜意敛,但我們需要理解其中環(huán)境類Context的作用乾戏,其結(jié)構(gòu)如下圖所示:
在策略模式結(jié)構(gòu)圖中包含如下幾個角色:
● Context(環(huán)境類):環(huán)境類是使用算法的角色爷辱,它在解決某個問題(即實現(xiàn)某個方法)時可以采用多種策略。在環(huán)境類中維持一個對抽象策略類的引用實例,用于定義所采用的策略台盯。
● Strategy(抽象策略類):它為所支持的算法聲明了抽象方法,是所有策略類的父類校读,它可以是抽象類或具體類胜卤,也可以是接口。環(huán)境類通過抽象策略類中聲明的方法在運行時調(diào)用具體策略類中實現(xiàn)的算法综膀。
● ConcreteStrategy(具體策略類):它實現(xiàn)了在抽象策略類中聲明的算法澳迫,在運行時,具體策略類將覆蓋在環(huán)境類中定義的抽象策略類對象剧劝,使用一種具體的算法實現(xiàn)某個業(yè)務(wù)處理橄登。
策略模式總結(jié)
策略模式用于算法的自由切換和擴展,它是應(yīng)用較為廣泛的設(shè)計模式之一讥此。策略模式對應(yīng)于解決某一問題的一個算法族拢锹,允許用戶從該算法族中任選一個算法來解決某一問題,同時可以方便地更換算法或者增加新的算法萄喳。只要涉及到算法的封裝卒稳、復(fù)用和切換都可以考慮使用策略模式。
- 主要優(yōu)點
策略模式的主要優(yōu)點如下:
(1) 策略模式提供了對“開閉原則”的完美支持他巨,用戶可以在不修改原有系統(tǒng)的基礎(chǔ)上選擇算法或行為展哭,也可以靈活地增加新的算法或行為湃窍。
(2) 策略模式提供了管理相關(guān)的算法族的辦法。策略類的等級結(jié)構(gòu)定義了一個算法或行為族匪傍,恰當使用繼承可以把公共的代碼移到抽象策略類中您市,從而避免重復(fù)的代碼。
(3) 策略模式提供了一種可以替換繼承關(guān)系的辦法役衡。如果不使用策略模式茵休,那么使用算法的環(huán)境類就可能會有一些子類,每一個子類提供一種不同的算法手蝎。但是榕莺,這樣一來算法的使用就和算法本身混在一起,不符合“單一職責原則”棵介,決定使用哪一種算法的邏輯和該算法本身混合在一起钉鸯,從而不可能再獨立演化;而且使用繼承無法實現(xiàn)算法或行為在程序運行時的動態(tài)切換邮辽。
(4) 使用策略模式可以避免多重條件選擇語句唠雕。多重條件選擇語句不易維護,它把采取哪一種算法或行為的邏輯與算法或行為本身的實現(xiàn)邏輯混合在一起吨述,將它們?nèi)坑簿幋a(Hard Coding)在一個龐大的多重條件選擇語句中岩睁,比直接繼承環(huán)境類的辦法還要原始和落后。
(5) 策略模式提供了一種算法的復(fù)用機制揣云,由于將算法單獨提取出來封裝在策略類中捕儒,因此不同的環(huán)境類可以方便地復(fù)用這些策略類。 - 主要缺點
策略模式的主要缺點如下:
(1) 客戶端必須知道所有的策略類邓夕,并自行決定使用哪一個策略類刘莹。這就意味著客戶端必須理解這些算法的區(qū)別,以便適時選擇恰當?shù)乃惴ǚ俑铡Q言之栋猖,策略模式只適用于客戶端知道所有的算法或行為的情況。
(2) 策略模式將造成系統(tǒng)產(chǎn)生很多具體策略類汪榔,任何細小的變化都將導(dǎo)致系統(tǒng)要增加一個新的具體策略類。
(3) 無法同時在客戶端使用多個策略類肃拜,也就是說痴腌,在使用策略模式時,客戶端每次只能使用一個策略類燃领,不支持使用一個策略類完成部分功能后再使用另一個策略類來完成剩余功能的情況士聪。 - 適用場景
在以下情況下可以考慮使用策略模式:
(1) 一個系統(tǒng)需要動態(tài)地在幾種算法中選擇一種,那么可以將這些算法封裝到一個個的具體算法類中猛蔽,而這些具體算法類都是一個抽象算法類的子類剥悟。換言之灵寺,這些具體算法類均有統(tǒng)一的接口,根據(jù)“里氏代換原則”和面向?qū)ο蟮亩鄳B(tài)性区岗,客戶端可以選擇使用任何一個具體算法類略板,并只需要維持一個數(shù)據(jù)類型是抽象算法類的對象。
(2) 一個對象有很多的行為慈缔,如果不用恰當?shù)哪J蕉3疲@些行為就只好使用多重條件選擇語句來實現(xiàn)。此時藐鹤,使用策略模式瓤檐,把這些行為轉(zhuǎn)移到相應(yīng)的具體策略類里面,就可以避免使用難以維護的多重條件選擇語句娱节。
(3) 不希望客戶端知道復(fù)雜的挠蛉、與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu),在具體策略類中封裝算法與相關(guān)的數(shù)據(jù)結(jié)構(gòu)肄满,可以提高算法的保密性與安全性谴古。
模板方法模式-Template Method Pattern
模板方法模式:定義一個操作中算法的框架,而將一些步驟延遲到子類中悄窃。模板方法模式使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟讥电。
模板方法模式是一種基于繼承的代碼復(fù)用技術(shù),它是一種類行為型模式轧抗。模板方法模式是結(jié)構(gòu)最簡單的行為型設(shè)計模式恩敌,在其結(jié)構(gòu)中只存在父類與子類之間的繼承關(guān)系。通過使用模板方法模式横媚,可以將一些復(fù)雜流程的實現(xiàn)步驟封裝在一系列基本方法中纠炮,在抽象父類中提供一個稱之為模板方法的方法來定義這些基本方法的執(zhí)行次序,而通過其子類來覆蓋某些步驟灯蝴,從而使得相同的算法框架可以有不同的執(zhí)行結(jié)果恢口。模板方法模式提供了一個模板方法來定義算法框架,而某些具體步驟的實現(xiàn)可以在其子類中完成穷躁。
模板方法模式結(jié)構(gòu)比較簡單耕肩,其核心是抽象類和其中的模板方法的設(shè)計,其結(jié)構(gòu)如下圖所示:
模板方法模式包含如下兩個角色:
(1) AbstractClass(抽象類):在抽象類中定義了一系列基本操作(PrimitiveOperations)问潭,這些基本操作可以是具體的猿诸,也可以是抽象的,每一個基本操作對應(yīng)算法的一個步驟狡忙,在其子類中可以重定義或?qū)崿F(xiàn)這些步驟梳虽。同時,在抽象類中實現(xiàn)了一個模板方法(Template Method)灾茁,用于定義一個算法的框架窜觉,模板方法不僅可以調(diào)用在抽象類中實現(xiàn)的基本方法谷炸,也可以調(diào)用在抽象類的子類中實現(xiàn)的基本方法禀挫,還可以調(diào)用其他對象中的方法。
(2) ConcreteClass(具體子類):它是抽象類的子類举瑰,用于實現(xiàn)在父類中聲明的抽象基本操作以完成子類特定算法的步驟搅窿,也可以覆蓋在父類中已經(jīng)實現(xiàn)的具體基本操作隔箍。
在實現(xiàn)模板方法模式時啦辐,開發(fā)抽象類的軟件設(shè)計師和開發(fā)具體子類的軟件設(shè)計師之間可以進行協(xié)作。一個設(shè)計師負責給出一個算法的輪廓和框架瘩缆,另一些設(shè)計師則負責給出這個算法的各個邏輯步驟斤儿。實現(xiàn)這些具體邏輯步驟的方法即為基本方法剧包,而將這些基本方法匯總起來的方法即為模板方法,模板方法模式的名字也因此而來往果。下面將詳細介紹模板方法和基本方法:
- 模板方法
一個模板方法是定義在抽象類中的疆液、把基本操作方法組合在一起形成一個總算法或一個總行為的方法。這個模板方法定義在抽象類中棚放,并由子類不加以修改地完全繼承下來枚粘。模板方法是一個具體方法,它給出了一個頂層邏輯框架飘蚯,而邏輯的組成步驟在抽象類中可以是具體方法馍迄,也可以是抽象方法。由于模板方法是具體方法局骤,因此模板方法模式中的抽象層只能是抽象類攀圈,而不是接口。 - 基本方法
基本方法是實現(xiàn)算法各個步驟的方法峦甩,是模板方法的組成部分赘来。基本方法又可以分為三種:抽象方法(Abstract Method)凯傲、具體方法(Concrete Method)和鉤子方法(Hook Method)犬辰。
- 抽象方法:一個抽象方法由抽象類聲明、由其具體子類實現(xiàn)冰单。在C#語言里一個抽象方法以abstract關(guān)鍵字標識幌缝。
- 具體方法:一個具體方法由一個抽象類或具體類聲明并實現(xiàn),其子類可以進行覆蓋也可以直接繼承诫欠。
- 鉤子方法:一個鉤子方法由一個抽象類或具體類聲明并實現(xiàn)涵卵,而其子類可能會加以擴展。模板方法模式中荒叼,在父類中提供了一個定義算法框架的模板方法轿偎,還提供了一系列抽象方法、具體方法和鉤子方法被廓,其中鉤子方法的引入使得子類可以控制父類的行為坏晦。鉤子方法有兩類:第一類鉤子方法可以與一些具體步驟“掛鉤”,以實現(xiàn)在不同條件下執(zhí)行模板方法中的不同步驟,這類鉤子方法的返回類型通常是bool類型的昆婿,這類方法名一般為IsXXX()间护,用于對某個條件進行判斷,如果條件滿足則執(zhí)行某一步驟挖诸,否則將不執(zhí)行;還有一類鉤子方法就是實現(xiàn)體為空的具體方法法精,子類可以根據(jù)需要覆蓋或者繼承這些鉤子方法多律,與抽象方法相比,這類鉤子方法的好處在于子類如果沒有覆蓋父類中定義的鉤子方法搂蜓,編譯可以正常通過狼荞,但是如果沒有覆蓋父類中聲明的抽象方法,編譯將報錯帮碰。
在模板方法模式中相味,由于面向?qū)ο蟮亩鄳B(tài)性,子類對象在運行時將覆蓋父類對象殉挽,子類中定義的方法也將覆蓋父類中定義的方法丰涉,因此程序在運行時,具體子類的基本方法將覆蓋父類中定義的基本方法斯碌,子類的鉤子方法也將覆蓋父類的鉤子方法一死,從而可以通過在子類中實現(xiàn)的鉤子方法對父類方法的執(zhí)行進行約束,實現(xiàn)子類對父類行為的反向控制傻唾。
模板方法模式總結(jié)
模板方法模式是基于繼承的代碼復(fù)用技術(shù)投慈,它體現(xiàn)了面向?qū)ο蟮闹T多重要思想,是一種使用較為頻繁的模式冠骄。模板方法模式廣泛應(yīng)用于框架設(shè)計中伪煤,以確保通過父類來控制處理流程的邏輯順序(如框架的初始化,測試流程的設(shè)置等)凛辣。
1.優(yōu)點
模板方法模式的主要優(yōu)點如下:
(1) 在父類中形式化地定義一個算法抱既,而由它的子類來實現(xiàn)細節(jié)的處理,在子類實現(xiàn)詳細的處理算法時并不會改變算法中步驟的執(zhí)行次序蟀给。
(2) 模板方法模式是一種代碼復(fù)用技術(shù)蝙砌,它在類庫設(shè)計中尤為重要,它提取了類庫中的公共行為跋理,將公共行為放在父類中择克,而通過其子類來實現(xiàn)不同的行為,它鼓勵我們恰當使用繼承來實現(xiàn)代碼復(fù)用前普。
(3) 可實現(xiàn)一種反向控制結(jié)構(gòu)肚邢,通過子類覆蓋父類的鉤子方法來決定某一特定步驟是否需要執(zhí)行。
(4) 在模板方法模式中可以通過子類來覆蓋父類的基本方法,不同的子類可以提供基本方法的不同實現(xiàn)骡湖,更換和增加新的子類很方便贱纠,符合單一職責原則和開閉原則。
2.模式缺點
模板方法模式的主要缺點如下:
需要為每一個基本方法的不同實現(xiàn)提供一個子類响蕴,如果父類中可變的基本方法太多谆焊,將會導(dǎo)致類的個數(shù)增加,系統(tǒng)更加龐大浦夷,設(shè)計也更加抽象辖试,此時,可結(jié)合橋接模式來進行設(shè)計劈狐。
- 模式適用場景
在以下情況下可以考慮使用模板方法模式:
(1) 對一些復(fù)雜的算法進行分割罐孝,將其算法中固定不變的部分設(shè)計為模板方法和父類具體方法,而一些可以改變的細節(jié)由其子類來實現(xiàn)肥缔。即:一次性實現(xiàn)一個算法的不變部分莲兢,并將可變的行為留給子類來實現(xiàn)。
(2) 各子類中公共的行為應(yīng)被提取出來并集中到一個公共父類中以避免代碼重復(fù)续膳。
(3) 需要通過子類來決定父類算法中某個步驟是否執(zhí)行改艇,實現(xiàn)子類對父類的反向控制。
訪問者模式-Visitor Pattern
如何為同一集合對象中的元素提供多種不同的操作方式坟岔?訪問者模式就是一個值得考慮的解決方案遣耍,它可以在一定程度上解決上述問題(解決大部分問題)。訪問者模式可以為為不同類型的元素提供多種訪問操作方式炮车,而且可以在不修改原有系統(tǒng)的情況下增加新的操作方式舵变。
訪問者模式是一種較為復(fù)雜的行為型設(shè)計模式,它包含訪問者和被訪問元素兩個主要組成部分瘦穆,這些被訪問的元素通常具有不同的類型纪隙,且不同的訪問者可以對它們進行不同的訪問操作。例如處方單中的各種藥品信息就是被訪問的元素扛或,而劃價人員和藥房工作人員就是訪問者绵咱。訪問者模式使得用戶可以在不修改現(xiàn)有系統(tǒng)的情況下擴展系統(tǒng)的功能,為這些不同類型的元素增加新的操作熙兔。
在使用訪問者模式時悲伶,被訪問元素通常不是單獨存在的,它們存儲在一個集合中住涉,這個集合被稱為“對象結(jié)構(gòu)”麸锉,訪問者通過遍歷對象結(jié)構(gòu)實現(xiàn)對其中存儲的元素的逐個操作。
訪問者模式的結(jié)構(gòu)較為復(fù)雜舆声,其結(jié)構(gòu)如下圖所示:
在訪問者模式結(jié)構(gòu)圖中包含如下幾個角色:
●Vistor(抽象訪問者):抽象訪問者為對象結(jié)構(gòu)中每一個具體元素類ConcreteElement聲明一個訪問操作花沉,從這個操作的名稱或參數(shù)類型可以清楚知道需要訪問的具體元素的類型柳爽,具體訪問者需要實現(xiàn)這些操作方法,定義對這些元素的訪問操作碱屁。
●ConcreteVisitor(具體訪問者):具體訪問者實現(xiàn)了每個由抽象訪問者聲明的操作磷脯,每一個操作用于訪問對象結(jié)構(gòu)中一種類型的元素。
●Element(抽象元素):抽象元素一般是抽象類或者接口娩脾,它定義一個accept()方法赵誓,該方法通常以一個抽象訪問者作為參數(shù)∈辽蓿【稍后將介紹為什么要這樣設(shè)計架曹。】
●ConcreteElement(具體元素):具體元素實現(xiàn)了accept()方法闹瞧,在accept()方法中調(diào)用訪問者的訪問方法以便完成對一個元素的操作。
● ObjectStructure(對象結(jié)構(gòu)):對象結(jié)構(gòu)是一個元素的集合展辞,它用于存放元素對象奥邮,并且提供了遍歷其內(nèi)部元素的方法。它可以結(jié)合組合模式來實現(xiàn)罗珍,也可以是一個簡單的集合對象洽腺,如一個List對象或一個Set對象。
訪問者模式中對象結(jié)構(gòu)存儲了不同類型的元素對象覆旱,以供不同訪問者訪問蘸朋。訪問者模式包括兩個層次結(jié)構(gòu),一個是訪問者層次結(jié)構(gòu)扣唱,提供了抽象訪問者和具體訪問者藕坯,一個是元素層次結(jié)構(gòu),提供了抽象元素和具體元素噪沙。相同的訪問者可以以不同的方式訪問不同的元素炼彪,相同的元素可以接受不同訪問者以不同訪問方式訪問。在訪問者模式中正歼,增加新的訪問者無須修改原有系統(tǒng)辐马,系統(tǒng)具有較好的可擴展性。
在訪問者模式中局义,抽象訪問者定義了訪問元素對象的方法喜爷,通常為每一種類型的元素對象都提供一個訪問方法,而具體訪問者可以實現(xiàn)這些訪問方法萄唇。這些訪問方法的命名一般有兩種方式:一種是直接在方法名中標明待訪問元素對象的具體類型檩帐,如visitElementA(ElementA elementA)贸街,還有一種是統(tǒng)一取名為visit()糖荒,通過參數(shù)類型的不同來定義一系列重載的visit()方法。當然蛾坯,如果所有的訪問者對某一類型的元素的訪問操作都相同,則可以將操作代碼移到抽象訪問者類中勾缭。
通過調(diào)用Visitor類的visit()方法實現(xiàn)對元素的訪問揍障,并以當前對象作為visit()方法的參數(shù)。其具體執(zhí)行過程如下:
(1) 調(diào)用具體元素類的accept(Visitor visitor)方法俩由,并將Visitor子類對象作為其參數(shù)毒嫡;
(2) 在具體元素類accept(Visitor visitor)方法內(nèi)部調(diào)用傳入的Visitor對象的visit()方法,如visit(ConcreteElementA elementA)幻梯,將當前具體元素類對象(this)作為參數(shù)兜畸,如visitor.visit(this);
(3) 執(zhí)行Visitor對象的visit()方法碘梢,在其中還可以調(diào)用具體元素對象的業(yè)務(wù)方法咬摇。
這種調(diào)用機制也稱為“雙重分派”,正因為使用了雙重分派機制煞躬,使得增加新的訪問者無須修改現(xiàn)有類庫代碼肛鹏,只需將新的訪問者對象作為參數(shù)傳入具體元素對象的accept()方法,程序運行時將回調(diào)在新增Visitor類中定義的visit()方法恩沛,從而增加新的元素訪問方式在扰。
訪問者模式與組合模式聯(lián)用
在訪問者模式中,包含一個用于存儲元素對象集合的對象結(jié)構(gòu)雷客,我們通趁⒅椋可以使用迭代器來遍歷對象結(jié)構(gòu),同時具體元素之間可以存在整體與部分關(guān)系搅裙,有些元素作為容器對象皱卓,有些元素作為成員對象,可以使用組合模式來組織元素部逮。引入組合模式后的訪問者模式結(jié)構(gòu)圖如下圖所示:
由于葉子元素的遍歷操作已經(jīng)在容器元素中完成好爬,因此要防止單獨將已增加到容器元素中的葉子元素再次加入對象結(jié)構(gòu)中,對象結(jié)構(gòu)中只保存容器元素和孤立的葉子元素甥啄。
訪問者模式總結(jié)
由于訪問者模式的使用條件較為苛刻存炮,本身結(jié)構(gòu)也較為復(fù)雜,因此在實際應(yīng)用中使用頻率不是特別高蜈漓。當系統(tǒng)中存在一個較為復(fù)雜的對象結(jié)構(gòu)穆桂,且不同訪問者對其所采取的操作也不相同時,可以考慮使用訪問者模式進行設(shè)計融虽。在XML文檔解析享完、編譯器的設(shè)計、復(fù)雜集合對象的處理等領(lǐng)域訪問者模式得到了一定的應(yīng)用有额。
1.主要優(yōu)點
訪問者模式的主要優(yōu)點如下:
(1) 增加新的訪問操作很方便般又。使用訪問者模式彼绷,增加新的訪問操作就意味著增加一個新的具體訪問者類,實現(xiàn)簡單茴迁,無須修改源代碼寄悯,符合“開閉原則”。
(2) 將有關(guān)元素對象的訪問行為集中到一個訪問者對象中堕义,而不是分散在一個個的元素類中猜旬。類的職責更加清晰,有利于對象結(jié)構(gòu)中元素對象的復(fù)用倦卖,相同的對象結(jié)構(gòu)可以供多個不同的訪問者訪問洒擦。
(3) 讓用戶能夠在不修改現(xiàn)有元素類層次結(jié)構(gòu)的情況下,定義作用于該層次結(jié)構(gòu)的操作怕膛。
2.主要缺點
訪問者模式的主要缺點如下:
(1) 增加新的元素類很困難熟嫩。在訪問者模式中,每增加一個新的元素類都意味著要在抽象訪問者角色中增加一個新的抽象操作褐捻,并在每一個具體訪問者類中增加相應(yīng)的具體操作掸茅,這違背了“開閉原則”的要求。
(2) 破壞封裝舍扰。訪問者模式要求訪問者對象訪問并調(diào)用每一個元素對象的操作,這意味著元素對象有時候必須暴露一些自己的內(nèi)部操作和內(nèi)部狀態(tài)希坚,否則無法供訪問者訪問边苹。
3.適用場景
在以下情況下可以考慮使用訪問者模式:
(1) 一個對象結(jié)構(gòu)包含多個類型的對象,希望對這些對象實施一些依賴其具體類型的操作裁僧。在訪問者中針對每一種具體的類型都提供了一個訪問操作个束,不同類型的對象可以有不同的訪問操作。
(2) 需要對一個對象結(jié)構(gòu)中的對象進行很多不同的并且不相關(guān)的操作聊疲,而需要避免讓這些操作“污染”這些對象的類茬底,也不希望在增加新操作時修改這些類。訪問者模式使得我們可以將相關(guān)的訪問操作集中起來定義在訪問者類中获洲,對象結(jié)構(gòu)可以被多個不同的訪問者類所使用阱表,將對象本身與對象的訪問操作分離。
(3) 對象結(jié)構(gòu)中對象對應(yīng)的類很少改變贡珊,但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作最爬。