設(shè)計(jì)模式的圣經(jīng)
????????提起設(shè)計(jì)模式,就不得不提《設(shè)計(jì)模式——可復(fù)用面向?qū)ο筌浖幕A(chǔ)》這本經(jīng)典著作。1995年,GOF(Gang Of Four),也就是Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides這四個(gè)人,合作出版了《Design Patterns: Elements of Reusable Object-Oriented Software》一書,被奉為設(shè)計(jì)模式的“圣經(jīng)”。
? ? ? ? ? ? ? ? ? ? ? ? ? ???
?????????該書描述了23種經(jīng)典的面向?qū)ο笤O(shè)計(jì)模式,確立了模式在軟件設(shè)計(jì)中的地位,開創(chuàng)了一種新的面向?qū)ο笤O(shè)計(jì)思潮。從此,參與設(shè)計(jì)模式研究的人數(shù)呈現(xiàn)爆炸性的增長。
設(shè)計(jì)模式簡介
????????□ 設(shè)計(jì)模式描述了在我們周圍不斷重復(fù)發(fā)生的問題,以及該問題的解決方案的核心。這樣,你就能一次又一次地使用該解決方案而不必重復(fù)勞動(dòng)。
????????□ 設(shè)計(jì)模式實(shí)際上就是類與相互通信的對(duì)象之間的組織關(guān)系,包括它們的角色、職責(zé)、協(xié)作方式等各個(gè)方面。
????????□ 設(shè)計(jì)模式通常和面向?qū)ο缶幊探Y(jié)合起來使用。面向?qū)ο笤O(shè)計(jì)模式是“好的面向?qū)ο笤O(shè)計(jì)”,所謂“好的面向?qū)ο笤O(shè)計(jì)”是指那些可以滿足 “應(yīng)對(duì)變化,提高復(fù)用”的設(shè)計(jì)。
????????□ 現(xiàn)代軟件設(shè)計(jì)的特征是:需求頻繁變化。設(shè)計(jì)模式的要點(diǎn)是:尋找變化點(diǎn),然后在變化點(diǎn)處應(yīng)用設(shè)計(jì)模式,從而來更好地應(yīng)對(duì)需求的變化。
軟件的復(fù)雜性
????????軟件的復(fù)雜性是一個(gè)很寬泛的概念,任何使軟件難以理解、難以修改、難以維護(hù)的東西,都屬于軟件的復(fù)雜性。軟件復(fù)雜的根本原因是:變化。這里的變化,包括:客戶需求的變化、技術(shù)平臺(tái)的變化、開發(fā)團(tuán)隊(duì)的變化、市場(chǎng)環(huán)境的變化等等。
????????IBM院士、IBM研究院軟件工程首席科學(xué)家Grady Booch曾經(jīng)說過下面這段話,有助于我們理解軟件的復(fù)雜性和需求變化的隨意性。
????????“建筑商從來不會(huì)去想給一棟已建好的100層高的樓房底下再新修一個(gè)小地下室——這樣做花費(fèi)極大而且注定要失敗。然而令人驚奇的是,軟件系統(tǒng)的用戶在要求作出類似改變時(shí)卻不會(huì)仔細(xì)考慮,而且他們認(rèn)為這只是需要簡單編程的事?!?/span>
解決復(fù)雜性
????????面向?qū)ο笤O(shè)計(jì)模式最大的優(yōu)勢(shì)在于:抵御變化。為什么能抵御變化呢?原因在于其遵循了以下的設(shè)計(jì)原則。
????????單一職責(zé)原則:一個(gè)類只負(fù)責(zé)一個(gè)職責(zé),只有一個(gè)引起它變化的原因。
????????開放封閉原則:對(duì)于功能擴(kuò)展是開放的,對(duì)于修改是封閉的。
????????里斯替換原則:子類可以替換基類,繼承必須確保超類所擁有的性質(zhì)在子類中仍然成立。
????????依賴倒置原則:高層次的模塊不應(yīng)該依賴于低層次的模塊,它們都應(yīng)該依賴于抽象。抽象不應(yīng)該依賴于具體,具體應(yīng)該依賴于抽象。
????????接口隔離原則:一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上。
????????迪米特法則:一個(gè)對(duì)象應(yīng)當(dāng)對(duì)其他對(duì)象有盡可能少的了解,不和陌生人說話。
????????封裝變化點(diǎn):使用封裝來創(chuàng)建對(duì)象之間的分界層,讓設(shè)計(jì)者可以在分界層的一側(cè)進(jìn)行修改,而不會(huì)對(duì)另一側(cè)產(chǎn)生不良的影響,從而實(shí)現(xiàn)層次間的松耦合。
????????優(yōu)先使用對(duì)象組合,而不是類繼承:類繼承通常為“白箱復(fù)用”,對(duì)象組合通常為“黑箱復(fù)用”。繼承在某種程度上破壞了封裝性,子類父類耦合度高。而對(duì)象組合則只要求被組合的對(duì)象具有良好定義的接口,耦合度低。
????????下面,我們通過4個(gè)具體的案例及其分析過程,來理解設(shè)計(jì)模式。
案例1
????????在面向?qū)ο笙到y(tǒng)的分析與設(shè)計(jì)過程中,經(jīng)常會(huì)遇到這樣一種情況:對(duì)于某一個(gè)業(yè)務(wù)邏輯(算法實(shí)現(xiàn)),在不同的對(duì)象中有不同的細(xì)節(jié)實(shí)現(xiàn), 但是邏輯(算法)的整體操作結(jié)構(gòu)和框架是穩(wěn)定的。如何在確保整體操作結(jié)構(gòu)穩(wěn)定的前提下,來靈活應(yīng)對(duì)不同的細(xì)節(jié)實(shí)現(xiàn)呢?
????????解決該問題,主要有以下三種思路:
????????1、封裝多個(gè)不同類,各實(shí)現(xiàn)各的(最多把共同的部分拎出來)—— 不使用設(shè)計(jì)模式。
????????2、采用繼承和多態(tài)的方式實(shí)現(xiàn) —— 模板方法(Template Method)模式。
????????3、采用組合和委托的方式實(shí)現(xiàn) —— 策略(Strategy)模式。
????????□ 模板方法模式
????????定義一個(gè)操作中的算法的骨架 (穩(wěn)定),而將一些步驟延遲(變化)到子類中。 Template Method使得子類可以不改變(復(fù)用)一個(gè)算法的結(jié)構(gòu)即可重定義(override 重寫)該算法的某些特定步驟。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
????????模板方法模式使用虛函數(shù)的多態(tài)性提供了靈活的擴(kuò)展點(diǎn),是代碼復(fù)用方面的基本實(shí)現(xiàn)結(jié)構(gòu)。但繼承的強(qiáng)制性約束關(guān)系也讓其有不足的地方,比如:ConcreteClass1中實(shí)現(xiàn)的原語方法Primitive1(),是不能被別的類復(fù)用的。(可類比:到銀行辦業(yè)務(wù))
????????□ 策略模式
????????定義一系列算法,把它們一個(gè)個(gè)封裝起來,并且使它們可互相替換(變化)。該模式使得算法可獨(dú)立于使用它的客戶程序(穩(wěn)定)而變化(擴(kuò)展,子類化)。
? ? ? ? ? ? ? ? ??
????????策略模式將邏輯(算法)封裝到一個(gè)類(Context)里面,通過組合的方式將具體算法的實(shí)現(xiàn)在組合對(duì)象中實(shí)現(xiàn),再通過委托的方式將抽象接口的實(shí)現(xiàn)委托給組合對(duì)象實(shí)現(xiàn)。策略模式可以在運(yùn)行時(shí)方便地根據(jù)需要在各個(gè)算法之間進(jìn)行切換,同時(shí)也解耦了穩(wěn)定部分和變化部分,變化部分很容易被其他類進(jìn)行復(fù)用。缺點(diǎn)是:多了一個(gè)類和一些接口;當(dāng)變化部分ConcreteStrategy需要使用穩(wěn)定部分Context中的內(nèi)部數(shù)據(jù)和方法時(shí),不太方便。(可類比:旅游出行)
案例2
????????在已經(jīng)封裝了一些類和接口的情況下,我們往往會(huì)發(fā)現(xiàn),如何使用這些類和接口有時(shí)也會(huì)成為一個(gè)不大不小的問題。原因在于,某些接口之間直接的依賴常常會(huì)帶來很多問題,甚至根本無法實(shí)現(xiàn)。舉幾個(gè)例子:
????????1、系統(tǒng)內(nèi)部存在很多子系統(tǒng),每個(gè)子系統(tǒng)都有各自的類和接口,客戶程序使用時(shí),需要調(diào)用各個(gè)子系統(tǒng)中的接口才能去完成某一項(xiàng)功能。隨著客戶程序和各子系統(tǒng)的演化,這種過多的耦合面臨很多變化的挑戰(zhàn)。如何簡化客戶程序和系統(tǒng)間的交互接口,并將客戶程序的演化與內(nèi)部子系統(tǒng)的變化之間的依賴相互解耦?
????????2、由于應(yīng)用環(huán)境的變化,有時(shí)候需要將一些現(xiàn)存的類和接口放到新的環(huán)境中去使用,但新環(huán)境要求的接口是現(xiàn)存接口所不滿足的。如何既能利用現(xiàn)有接口的良好實(shí)現(xiàn),同時(shí)又能滿足新的應(yīng)用環(huán)境?
????????3、在面向?qū)ο笙到y(tǒng)中,直接創(chuàng)建某些類的對(duì)象會(huì)給使用者或系統(tǒng)結(jié)構(gòu)帶來很多麻煩。比如:有一個(gè)圖片對(duì)象的類,創(chuàng)建它時(shí),會(huì)去磁盤或網(wǎng)絡(luò)加載圖片,如果顯示的圖片數(shù)量非常多,創(chuàng)建的開銷是非常大的。如何在不修改已有類和接口的情況下,來管理和控制這些對(duì)象特有的復(fù)雜性呢?
?
????????上述的三個(gè)問題,都屬于接口隔離的范疇,分別為:
????????1、在高層提供統(tǒng)一的接口,內(nèi)部去調(diào)用各子系統(tǒng)的接口 —— 外觀(Facade)模式。
????????2、采用繼承和組合的方式實(shí)現(xiàn)一個(gè)適配器類,用于適配新老接口 —— 適配器(Adapter)模式。
????????3、采用組合和委托的方式實(shí)現(xiàn)一個(gè)代理類 —— 代理(Proxy)模式。
????????□ 外觀模式
????????為子系統(tǒng)中的一組接口提供一個(gè)一致(穩(wěn)定)的界面。Facade模式定義了一個(gè)高級(jí)接口,這個(gè)接口使得子系統(tǒng)更加容易使用。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
????????外觀模式不僅簡化了整個(gè)系統(tǒng)的接口,對(duì)于客戶程序和內(nèi)部子系統(tǒng)來說,還達(dá)到了一種解耦的效果 —— 內(nèi)部子系統(tǒng)的任何變化不會(huì)影響到Facade接口的變化。(可類比:到政府機(jī)構(gòu)辦事)
????????□ 適配器模式
????????將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另一個(gè)接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
? ? ? ? ? ? ? ? ? ? ? ? ? ??
????????適配器模式主要應(yīng)用于希望復(fù)用一些現(xiàn)存的類,但接口又與新的使用環(huán)境的要求不一致的情況,在遺留代碼復(fù)用、類庫遷移等方面非常有用。(可類比:筆記本的電源適配器、SD卡讀卡器)
????????□ 代理模式
????????為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。
? ? ? ? ? ? ? ? ? ? ? ?
????????代理模式通過增加一層間接層的方法,有效解決了面向?qū)ο笙到y(tǒng)中直接使用某些對(duì)象帶來的問題。這些不好直接使用的對(duì)象主要包括:開銷大的對(duì)象、網(wǎng)絡(luò)上的對(duì)象、需要進(jìn)行權(quán)限控制的對(duì)象等。(可類比:房產(chǎn)中介)????????
案例3
????????常常有一些對(duì)象在內(nèi)部具有特定的數(shù)據(jù)結(jié)構(gòu),如果讓客戶程序依賴這些特定的數(shù)據(jù)結(jié)構(gòu),將導(dǎo)致客戶程序與這些對(duì)象產(chǎn)生極大的耦合。存在以下兩種通用情形:
????????1、對(duì)象內(nèi)部包含一個(gè)容器,客戶程序想要訪問容器內(nèi)的信息時(shí),有兩種方式:一是直接把容器通過接口返回給客戶程序,由客戶程序去遍歷;二是對(duì)象自身提供接口遍歷容器內(nèi)的每個(gè)元素。第一種方式耦合性太高,容器結(jié)構(gòu)的變化會(huì)導(dǎo)致客戶程序的變化。第二種方式雖然能夠工作,但不夠通用,所有容器對(duì)象都需要提供一套類似的接口。如何在不暴露內(nèi)部數(shù)據(jù)結(jié)構(gòu)的同時(shí),又能優(yōu)雅透明地訪問其中的元素呢?
????????2、對(duì)象內(nèi)部是一個(gè)樹狀結(jié)構(gòu),如何通過接口去訪問對(duì)象內(nèi)的樹節(jié)點(diǎn)?
????????上述兩個(gè)問題,均與數(shù)據(jù)結(jié)構(gòu)有關(guān)。
????????1、在客戶程序與聚合對(duì)象之間插入一個(gè)迭代器,由迭代器專門負(fù)責(zé)遍歷工作,這分離了聚合對(duì)象與其遍歷行為,對(duì)客戶也隱藏了其內(nèi)部細(xì)節(jié) —— 迭代器(Iterator)模式。
????????2、使用繼承的方式實(shí)現(xiàn)葉子類和組合類,葉子類和組合類派生自一個(gè)公共的基類 —— 組合(Composite)模式。
????????□ 迭代器模式
????????提供一種方法順序訪問一個(gè)聚合對(duì)象中的各個(gè)元素,而又不暴露(穩(wěn)定)該對(duì)象的內(nèi)部表示。修改遍歷方式時(shí),不需要修改聚合對(duì)象,只需要修改迭代器即可。
? ? ? ? ? ? ? ? ??
????????迭代器模式為遍歷不同的集合結(jié)構(gòu)提供了一個(gè)統(tǒng)一的接口,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進(jìn)行操作。實(shí)際開發(fā)時(shí),迭代器可能需要訪問聚合對(duì)象的內(nèi)部數(shù)據(jù)結(jié)構(gòu),因此聚合對(duì)象需要將迭代器設(shè)置為友元。(可類比:流水線上的快遞包裹)
????????□ 組合模式
????????將對(duì)象組合成樹形結(jié)構(gòu)以表示“部分 - 整體”的層次結(jié)構(gòu)。Composite使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性(穩(wěn)定)。
? ? ? ? ? ??
????????組合模式采用樹形結(jié)構(gòu)將一對(duì)多的關(guān)系轉(zhuǎn)換為一對(duì)一的關(guān)系,使得客戶程序可以一致地處理葉子對(duì)象和組合對(duì)象。組合模式去耦合后,客戶程序?qū)⒅灰蕾囉诩兇獾某橄蠼涌?,從而更能?yīng)對(duì)變化。(可類比:目錄和文件)
案例4
????????在軟件的開發(fā)過程中,隨著需求的不斷變化和增加,類的個(gè)數(shù)也會(huì)不斷變多,最終會(huì)導(dǎo)致類的急劇膨脹。
????????1、當(dāng)需要為一個(gè)已經(jīng)定義好的類添加新的職責(zé)時(shí),通常情況下我們會(huì)定義一個(gè)新類繼承已定義好的類。添加的職責(zé)越多,定義的新類就會(huì)越多,最終導(dǎo)致很深層次的繼承關(guān)系和數(shù)不清的類。如果考慮到各種職責(zé)的組合,則會(huì)導(dǎo)致更多子類的膨脹和重復(fù)代碼的產(chǎn)生。如何使對(duì)象職責(zé)的擴(kuò)展能夠根據(jù)需要來動(dòng)態(tài)地實(shí)現(xiàn),同時(shí)避免帶來的子類膨脹問題?
????????2、當(dāng)一個(gè)類有一個(gè)變化的維度時(shí),我們會(huì)派生出兩個(gè)類。當(dāng)一個(gè)類有兩個(gè)變化的維度時(shí),我們會(huì)派生出四個(gè)類。變化的維度越多,派生出的類也會(huì)越多。如何利用面向?qū)ο蠹夹g(shù)來使得類型可以輕松地沿著兩個(gè)乃至多個(gè)維度變化,而不引入額外的復(fù)雜度?
????????如何避免需求變化和增加導(dǎo)致的復(fù)雜性呢?可以考慮使用單一職責(zé)的設(shè)計(jì)原則。
????????1、通過組合的方式給一個(gè)對(duì)象添加新的職責(zé),原始的類不需要修改和派生,添加新的裝飾類即可 —— 裝飾(Decorator)模式。
????????2、將系統(tǒng)分為兩個(gè)獨(dú)立的部分:抽象部分和實(shí)現(xiàn)部分,兩個(gè)部分可以獨(dú)立地進(jìn)行修改,然后通過組合的方式進(jìn)行橋接 —— 橋接(Bridge)模式。
????????□ 裝飾模式
????????動(dòng)態(tài)(組合)地給一個(gè)對(duì)象增加一些額外的職責(zé)。就增加功能而言,Decorator模式比生成子類(繼承)更為靈活(消除重復(fù)代碼 & 減少子類個(gè)數(shù))。
? ? ? ? ? ? ? ? ? ? ?
????????裝飾模式通過組合而非繼承的方式,實(shí)現(xiàn)了在運(yùn)行時(shí)動(dòng)態(tài)擴(kuò)展對(duì)象職責(zé)的能力,而且可以根據(jù)需要擴(kuò)展多個(gè)職責(zé),避免了使用繼承帶來的靈活性差和多子類衍生問題。Decorator類在接口上表現(xiàn)為is-a Component的繼承關(guān)系,即Decorator類繼承了Component類所具有的接口。但在實(shí)現(xiàn)上又表現(xiàn)為has-a Component的組合關(guān)系,即Decorator類又使用了另外一個(gè)Component類。(可類比:雜糧餅)
????????□ 橋接模式
????????將抽象部分(業(yè)務(wù)功能)與實(shí)現(xiàn)部分(平臺(tái)實(shí)現(xiàn))分離,使它們都可以獨(dú)立地變化。
? ? ?
????????橋接模式使用“對(duì)象間的組合關(guān)系”解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系,使得抽象和實(shí)現(xiàn)可以沿著各自的維度來變化。Bridge模式有時(shí)候類似于多繼承方案,但是多繼承方案往往違背單一職責(zé)原則,復(fù)用性比較差,Bridge模式是比多繼承方案更好的解決方法。(可類比:蠟筆和毛筆)
總結(jié)
????????□ 使用設(shè)計(jì)模式時(shí),一定要區(qū)分需求的穩(wěn)定部分和可變部分。一個(gè)軟件必然有穩(wěn)定部分,這個(gè)部分就是核心業(yè)務(wù)邏輯。如果核心業(yè)務(wù)邏輯發(fā)生變化,軟件就沒有存在的必要,核心業(yè)務(wù)邏輯是我們需要固化的。對(duì)于可變的部分,需要判斷可能發(fā)生變化的程度來確定設(shè)計(jì)策略和設(shè)計(jì)風(fēng)險(xiǎn)。
????????□ 考慮你的設(shè)計(jì)中什么可能會(huì)發(fā)生變化,考慮你允許什么發(fā)生變化而不讓這一變化導(dǎo)致重新設(shè)計(jì)。設(shè)計(jì)模式的核心在于發(fā)現(xiàn)變化點(diǎn),并封裝之。另外,一種可變性不應(yīng)散落在代碼的很多角落,一種可變性也不應(yīng)當(dāng)與另一種可變性混合在一起。
????????□ 在實(shí)際工作中,很少會(huì)規(guī)定必須使用哪些設(shè)計(jì)模式,這樣只會(huì)帶來限制和條條框框。不能為了使用設(shè)計(jì)模式而去做架構(gòu),而是有了做架構(gòu)的需求后,發(fā)現(xiàn)它符合某一類設(shè)計(jì)模式的結(jié)構(gòu),再將兩者結(jié)合。
????????□ 設(shè)計(jì)模式要活學(xué)活用,不要生搬硬套。死記硬背是沒用的,還要從實(shí)踐中理解。想要游刃有余地使用設(shè)計(jì)模式,需要打下牢固的程序設(shè)計(jì)語言基礎(chǔ),夯實(shí)自己的編程思想,積累大量的時(shí)間經(jīng)驗(yàn),提高開發(fā)能力。
????????□ 設(shè)計(jì)模式從來都不是單個(gè)設(shè)計(jì)模式獨(dú)立使用的。在實(shí)際應(yīng)用中,通常多個(gè)設(shè)計(jì)模式混合使用,你中有我,我中有你。
????????□ 軟件開發(fā)是一項(xiàng)實(shí)踐工作,最直接的方法就是編程。沒有從來不下棋卻熟悉定式的圍棋高手。掌握設(shè)計(jì)模式是水到渠成的事情,不要強(qiáng)求。隨著理論和實(shí)踐的不斷積累,可能會(huì)“漸悟”或者“頓悟”。
????????□ 設(shè)計(jì)模式解決的是設(shè)計(jì)不足的問題,但同時(shí)也要避免設(shè)計(jì)過度。一定要牢記簡潔原則,要知道設(shè)計(jì)模式是為了使設(shè)計(jì)簡單,而不是更復(fù)雜。如果引入設(shè)計(jì)模式使得設(shè)計(jì)變得復(fù)雜,只能說我們把簡單問題復(fù)雜化了,問題本身不需要設(shè)計(jì)模式。
????????□ 設(shè)計(jì)模式的應(yīng)用不宜先入為主,一上來就使用設(shè)計(jì)模式是對(duì)設(shè)計(jì)模式的最大誤用。沒有一步到位的設(shè)計(jì)模式。敏捷軟件開發(fā)實(shí)踐提倡的“Refactoring to Patterns”(重構(gòu)獲得模式),是目前普遍公認(rèn)的最好的使用設(shè)計(jì)模式的方法。
????????□ 或許有的人會(huì)說,我們不需要設(shè)計(jì)模式,我們的系統(tǒng)很小,設(shè)計(jì)模式會(huì)束縛我們的實(shí)現(xiàn)。實(shí)際上,設(shè)計(jì)模式體現(xiàn)的是一種思想,而思想則是指導(dǎo)行為的一切。理解和掌握了設(shè)計(jì)模式,并不是說記牢了23種或更多的設(shè)計(jì)場(chǎng)景和解決策略,實(shí)際接受的是一種思想的熏陶和洗禮。等這種思想融入到了你的思想中后,你就會(huì)不自覺地使用這種思想去進(jìn)行你的設(shè)計(jì)和開發(fā),而這才是最重要的。文章來源:http://www.zghlxwxcb.cn/news/detail-464472.html
推薦書籍
? ???
? ? ? ?
文章來源地址http://www.zghlxwxcb.cn/news/detail-464472.html
到了這里,關(guān)于軟件工程師,不懂點(diǎn)設(shè)計(jì)模式怎么行的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!