設計模式總結
- 設計模式簡述
-
- 設計模式原則(7條)
-
- 開閉原則
- 裡氏替換原則
- 依賴倒置原則
- 單一職責原則
- 接口隔離原則
- 迪米特法則
- 合成複用原則
- 設計模式的分類
-
- 根據目的分類
- 根據作用範圍分類
- 分類表格
- 設計模式定義與場景(23種)
-
- 單例模式
- 原型模式
- 工廠模式
- 抽象工廠模式
- 建造者模式
- 代理模式
- 擴充卡模式
- 橋接模式
- 裝飾模式
- 外觀模式
- 享元模式
- 組合模式
- 模闆方法模式
- 政策模式
- 指令模式
- 責任鍊模式
- 狀态模式
- 觀察者模式
- 中介者模式
- 疊代器模式
- 通路者模式
- 備忘錄模式
- 解釋器模式
- 問題場景分析
-
- 場景一 - 模拟美國總統
- 場景二 - 畜牧場
- 場景三 - 五子棋
- 場景四 - 學生成績狀态轉換
- 場景五 - 皮包選購
- 場景六 - 汽車發動機
- 專業術語了解
-
- 複用
- 穩定與變化
- 編譯時依賴與運作時依賴
- 類繼承與對象組合
- 依賴倒置原則
- 面向接口程式設計
設計模式簡述
設計模式原則(7條)
開閉原則
開閉原則(Open Closed Principle,OCP)由勃蘭特·梅耶(Bertrand Meyer)提出,他在 1988 年的著作《面向對象軟體構造》(Object Oriented Software Construction)中提出:軟體實體應當對擴充開放,對修改關閉(Software entities should be open for extension,but closed for modification),這就是開閉原則的經典定義。
含義
當應用的需求改變時,在不修改軟體實體的源代碼或者二進制代碼的前提下,可以擴充子產品的功能,使其滿足新的需求。
作用
對軟體測試的影響。
提高代碼的可複用性。
提高軟體的可維護性。
裡氏替換原則
裡氏替換原則(Liskov Substitution Principle,LSP)由麻省理工學院計算機科學實驗室的裡斯科夫(Liskov)女士在 1987 年的“面向對象技術的高峰會議”(OOPSLA)上發表的一篇文章《資料抽象和層次》(Data Abstraction and Hierarchy)裡提出來的,她提出:繼承必須確定超類所擁有的性質在子類中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。
裡氏替換原則主要闡述了有關繼承的一些原則,也就是什麼時候應該使用繼承,什麼時候不應該使用繼承,以及其中蘊含的原理。裡氏替換原是繼承複用的基礎,它反映了基類與子類之間的關系,是對開閉原則的補充,是對實作抽象化的具體步驟的規範。
作用
- 裡氏替換原則是實作開閉原則的重要方式之一。
- 它克服了繼承中重寫父類造成的可複用性變差的缺點。
- 它是動作正确性的保證。即類的擴充不會給已有的系統引入新的錯誤,降低了代碼出錯的可能性。
依賴倒置原則
依賴倒置原則的原始定義為:高層子產品不應該依賴低層子產品,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。其核心思想是:要面向接口程式設計,不要面向實作程式設計。
依賴倒置原則是實作開閉原則的重要途徑之一,它降低了客戶與實作子產品之間的耦合。
由于在軟體設計中,細節具有多變性,而抽象層則相對穩定,是以以抽象為基礎搭建起來的架構要比以細節為基礎搭建起來的架構要穩定得多。這裡的抽象指的是接口或者抽象類,而細節是指具體的實作類。
使用接口或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操作,把展現細節的任務交給它們的實作類去完成。
作用
- 依賴倒置原則可以降低類間的耦合性。
- 依賴倒置原則可以提高系統的穩定性。
- 依賴倒置原則可以減少并行開發引起的風險。
- 依賴倒置原則可以提高代碼的可讀性和可維護性。
單一職責原則
單一職責原則(Single Responsibility Principle,SRP)又稱單一功能原則,由羅伯特·C.馬丁(Robert C. Martin)于《靈活軟體開發:原則、模式和實踐》一書中提出的。這裡的職責是指類變化的原因,單一職責原則規定一個類應該有且僅有一個引起它變化的原因,否則類應該被拆分(There should never be more than one reason for a class to change)。
該原則提出對象不應該承擔太多職責,如果一個對象承擔了太多的職責,至少存在以下兩個缺點:
一個職責的變化可能會削弱或者抑制這個類實作其他職責的能力;
當用戶端需要該對象的某一個職責時,不得不将其他不需要的職責全都包含進來,進而造成備援代碼或代碼的浪費。
優點
- 降低類的複雜度。一個類隻負責一項職責,其邏輯肯定要比負責多項職責簡單得多。
- 提高類的可讀性。複雜性降低,自然其可讀性會提高。
- 提高系統的可維護性。可讀性提高,那自然更容易維護了。
- 變更引起的風險降低。變更是必然的,如果單一職責原則遵守得好,當修改一個功能時,可以顯著降低對其他功能的影響。
接口隔離原則
接口隔離原則(Interface Segregation Principle,ISP)要求程式員盡量将臃腫龐大的接口拆分成更小的和更具體的接口,讓接口中隻包含客戶感興趣的方法。
2002 年羅伯特·C.馬丁給“接口隔離原則”的定義是:用戶端不應該被迫依賴于它不使用的方法(Clients should not be forced to depend on methods they do not use)。該原則還有另外一個定義:一個類對另一個類的依賴應該建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。
以上兩個定義的含義是:要為各個類建立它們需要的專用接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調用。
優點
- 接口隔離原則是為了限制接口、降低類對接口的依賴性,遵循接口隔離原則有以下 5 個優點。
- 将臃腫龐大的接口分解為多個粒度小的接口,可以預防外來變更的擴散,提高系統的靈活性和可維護性。
- 接口隔離提高了系統的内聚性,減少了對外互動,降低了系統的耦合性。
- 如果接口的粒度大小定義合理,能夠保證系統的穩定性;但是,如果定義過小,則會造成接口數量過多,使設計複雜化;如果定義太大,靈活性降低,無法提供定制服務,給整體項目帶來無法預料的風險。
- 使用多個專門的接口還能夠展現對象的層次,因為可以通過接口的繼承,實作對總接口的定義。
- 能減少項目工程中的代碼備援。過大的大接口裡面通常放置許多不用的方法,當實作這個接口的時候,被迫設計備援的代碼。
迪米特法則
迪米特法則(Law of Demeter,LoD)又叫作最少知識原則(Least Knowledge Principle,LKP),産生于 1987 年美國東北大學(Northeastern University)的一個名為迪米特(Demeter)的研究項目,由伊恩·荷蘭(Ian Holland)提出,被 UML 創始者之一的布奇(Booch)普及,後來又因為在經典著作《程式員修煉之道》(The Pragmatic Programmer)提及而廣為人知。
迪米特法則的定義是:隻與你的直接朋友交談,不跟“陌生人”說話(Talk only to your immediate friends and not to strangers)。其含義是:如果兩個軟體實體無須直接通信,那麼就不應當發生直接的互相調用,可以通過第三方轉發該調用。其目的是降低類之間的耦合度,提高子產品的相對獨立性。
迪米特法則中的“朋友”是指:目前對象本身、目前對象的成員對象、目前對象所建立的對象、目前對象的方法參數等,這些對象同目前對象存在關聯、聚合或組合關系,可以直接通路這些對象的方法。
優點
- 降低了類之間的耦合度,提高了子產品的相對獨立性。
- 由于親合度降低,進而提高了類的可複用率和系統的擴充性。
合成複用原則
合成複用原則(Composite Reuse Principle,CRP)又叫組合/聚合複用原則(Composition/Aggregate Reuse Principle,CARP)。它要求在軟體複用時,要盡量先使用組合或者聚合等關聯關系來實作,其次才考慮使用繼承關系來實作。
如果要使用繼承關系,則必須嚴格遵循裡氏替換原則。合成複用原則同裡氏替換原則相輔相成的,兩者都是開閉原則的具體實作規範。
優缺點
通常類的複用分為繼承複用和合成複用兩種,繼承複用雖然有簡單和易實作的優點,但它也存在以下缺點。
- 繼承複用破壞了類的封裝性。因為繼承會将父類的實作細節暴露給子類,父類對子類是透明的,是以這種複用又稱為“白箱”複用。
- 子類與父類的耦合度高。父類的實作的任何改變都會導緻子類的實作發生變化,這不利于類的擴充與維護。
- 它限制了複用的靈活性。從父類繼承而來的實作是靜态的,在編譯時已經定義,是以在運作時不可能發生變化。
采用組合或聚合複用時,可以将已有對象納入新對象中,使之成為新對象的一部分,新對象可以調用已有對象的功能,它有以下優點。
- 它維持了類的封裝性。因為成分對象的内部細節是新對象看不見的,是以這種複用又稱為“黑箱”複用。
- 新舊類之間的耦合度低。這種複用所需的依賴較少,新對象存取成分對象的唯一方法是通過成分對象的接口。
- 複用的靈活性高。這種複用可以在運作時動态進行,新對象可以動态地引用與成分對象類型相同的對象。
設計模式的分類
根據目的分類
根據模式是用來完成什麼工作來劃分,這種方式可分為建立型模式、結構型模式和行為型模式 3 種。
建立型模式:用于描述“怎樣建立對象”,它的主要特點是“将對象的建立與使用分離”。GoF 中提供了單例、原型、工廠方法、抽象工廠、建造者等 5 種建立型模式。
結構型模式:用于描述如何将類或對象按某種布局組成更大的結構,GoF 中提供了代理、擴充卡、橋接、裝飾、外觀、享元、組合等 7 種結構型模式。
行為型模式:用于描述類或對象之間怎樣互相協作共同完成單個對象都無法單獨完成的任務,以及怎樣配置設定職責。GoF 中提供了模闆方法、政策、指令、職責鍊、狀态、觀察者、中介者、疊代器、通路者、備忘錄、解釋器等 11 種行為型模式。
根據作用範圍分類
根據模式是主要用于類上還是主要用于對象上來分,這種方式可分為類模式和對象模式兩種。
類模式:用于處理類與子類之間的關系,這些關系通過繼承來建立,是靜态的,在編譯時刻便确定下來了。GoF中的工廠方法、(類)擴充卡、模闆方法、解釋器屬于該模式。
對象模式:用于處理對象之間的關系,這些關系可以通過組合或聚合來實作,在運作時刻是可以變化的,更具動态性。GoF 中除了以上 4 種,其他的都是對象模式。
分類表格
範圍\目的 | 建立型模式 | 結構型模式 | 行為型模式 |
---|---|---|---|
類模式 | 工廠方法 | (類)擴充卡 | 模闆方法 擴充卡 |
對象模式 | 單例 原型 抽象工廠 建造者 | 代理(對象) 擴充卡 橋接 裝飾 外觀 享元 組合 | 政策 指令 職責鍊 狀态 觀察者 中介者 疊代器 通路者 備忘錄 |
設計模式定義與場景(23種)
單例模式
定義
單例(Singleton)模式的定義:指一個類隻有一個執行個體,且該類能自行建立這個執行個體的一種模式。例如,Windows 中隻能打開一個任務管理器,這樣可以避免因打開多個任務管理器視窗而造成記憶體資源的浪費,或出現各個視窗顯示内容的不一緻等錯誤。
特點
單例模式有 3 個特點:
單例類隻有一個執行個體對象;
該單例對象必須由單例類自行建立;
單例類對外提供一個通路該單例的全局通路點;
應用場景
在應用場景中,某類隻要求生成一個對象的時候,如一個班中的班長、每個人的身份證号等。
當對象需要被共享的場合。由于單例模式隻允許建立一個對象,共享該對象可以節省記憶體,并加快對象通路速度。如 Web 中的配置對象、資料庫的連接配接池等。
當某類需要頻繁執行個體化,而建立的對象又頻繁被銷毀的時候,如多線程的線程池、網絡連接配接池等。
原型模式
定義
原型(Prototype)模式的定義如下:用一個已經建立的執行個體作為原型,通過複制該原型對象來建立一個和原型相同或相似的新對象。
特點
原型執行個體指定了要建立的對象的種類。用這種方式建立對象非常高效,根本無須知道對象建立的細節。
應用場景
對象之間相同或相似,即隻是個别的幾個屬性不同的時候。
對象的建立過程比較麻煩,但複制比較簡單的時候。
工廠模式
定義
工廠方法(FactoryMethod)模式的定義:定義一個建立産品對象的工廠接口,将産品對象的實際建立工作推遲到具體子工廠類當中。這滿足建立型模式中所要求的“建立與使用相分離”的特點。
我們把被建立的對象稱為“産品”,把建立産品的對象稱為“工廠”。如果要建立的産品不多,隻要一個工廠類就可以完成,這種模式叫“簡單工廠模式”,它不屬于 GoF 的 23 種經典設計模式,它的缺點是增加新産品時會違背“開閉原則”。
優點
- 使用者隻需要知道具體工廠的名稱就可得到所要的産品,無須知道産品的具體建立過程;
- 在系統增加新的産品時隻需要添加具體産品類和對應的具體工廠類,無須對原工廠進行任何修改,滿足開閉原則;
缺點
每增加一個産品就要增加一個具體産品類和一個對應的具體工廠類,這增加了系統的複雜度。
應用場景
- 客戶隻知道建立産品的工廠名,而不知道具體的産品名。如 TCL 電視工廠、海信電視工廠等。
- 建立對象的任務由多個具體子工廠中的某一個完成,而抽象工廠隻提供建立産品的接口。
- 客戶不關心建立産品的細節,隻關心産品的品牌。
抽象工廠模式
定義
抽象工廠(AbstractFactory)模式的定義:是一種為通路類提供一個建立一組相關或互相依賴對象的接口,且通路類無須指定所要産品的具體類就能得到同族的不同等級的産品的模式結構。
抽象工廠模式是工廠方法模式的更新版本,工廠方法模式隻生産一個等級的産品,而抽象工廠模式可生産多個等級的産品。
使用抽象工廠模式一般要滿足以下條件:
系統中有多個産品族,每個具體工廠建立同一族但屬于不同等級結構的産品。
系統一次隻可能消費其中某一族産品,即同族的産品一起使用。
優點
- 抽象工廠模式除了具有工廠方法模式的優點外,其他主要優點如下。
- 可以在類的内部對産品族中相關聯的多等級産品共同管理,而不必專門引入多個新的類來進行管理。
- 當增加一個新的産品族時不需要修改原代碼,滿足開閉原則。
缺點
當産品族中需要增加一個新的産品時,所有的工廠類都需要進行修改。
應用場景
- 當需要建立的對象是一系列互相關聯或互相依賴的産品族時,如電器工廠中的電視機、洗衣機、空調等。
- 系統中有多個産品族,但每次隻使用其中的某一族産品。如有人隻喜歡穿某一個品牌的衣服和鞋。
- 系統中提供了産品的類庫,且所有産品的接口相同,用戶端不依賴産品執行個體的建立細節和内部結構。
建造者模式
定義
建造者(Builder)模式的定義:指将一個複雜對象的構造與它的表示分離,使同樣的建構過程可以建立不同的表示,這樣的設計模式被稱為建造者模式。它是将一個複雜的對象分解為多個簡單的對象,然後一步一步建構而成。它将變與不變相分離,即産品的組成部分是不變的,但每一部分是可以靈活選擇的。
優點
各個具體的建造者互相獨立,有利于系統的擴充。
用戶端不必知道産品内部組成的細節,便于控制細節風險。
缺點
産品的組成部分必須相同,這限制了其使用範圍。
如果産品的内部變化複雜,該模式會增加很多的建造者類。
應用場景
- 建造者(Builder)模式建立的是複雜對象,其産品的各個部分經常面臨着劇烈的變化,但将它們組合在一起的算法卻相對穩定,是以它通常在以下場合使用。
- 建立的對象較複雜,由多個部件構成,各部件面臨着複雜的變化,但構件間的建造順序是穩定的。
- 建立複雜對象的算法獨立于該對象的組成部分以及它們的裝配方式,即産品的建構過程和最終的表示是獨立的。
代理模式
定義
代理模式的定義:由于某些原因需要給某對象提供一個代理以控制對該對象的通路。這時,通路對象不适合或者不能直接引用目标對象,代理對象作為通路對象和目标對象之間的中介。
優點
- 代理模式在用戶端與目标對象之間起到一個中介作用和保護目标對象的作用;
- 代理對象可以擴充目标對象的功能;
- 代理模式能将用戶端與目标對象分離,在一定程度上降低了系統的耦合度;
缺點
在用戶端和目标對象之間增加一個代理對象,會造成請求處理速度變慢;
增加了系統的複雜度;
應用場景
- 遠端代理,這種方式通常是為了隐藏目标對象存在于不同位址空間的事實,友善用戶端通路。例如,使用者申請某些網盤空間時,會在使用者的檔案系統中建立一個虛拟的硬碟,使用者通路虛拟硬碟時實際通路的是網盤空間。
- 虛拟代理,這種方式通常用于要建立的目标對象開銷很大時。例如,下載下傳一幅很大的圖像需要很長時間,因某種計算比較複雜而短時間無法完成,這時可以先用小比例的虛拟代理替換真實的對象,消除使用者對伺服器慢的感覺。
- 安全代理,這種方式通常用于控制不同種類客戶對真實對象的通路權限。
- 智能指引,主要用于調用目标對象時,代理附加一些額外的處理功能。例如,增加計算真實對象的引用次數的功能,這樣當該對象沒有被引用時,就可以自動釋放它。
- 延遲加載,指為了提高系統的性能,延遲對目标的加載。例如,Hibernate 中就存在屬性的延遲加載和關聯表的延時加載。
擴充卡模式
定義
擴充卡模式(Adapter)的定義如下:将一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不相容而不能一起工作的那些類能一起工作。擴充卡模式分為類結構型模式和對象結構型模式兩種,前者類之間的耦合度比後者高,且要求程式員了解現有元件庫中的相關元件的内部結構,是以應用相對較少些。
優點
- 用戶端通過擴充卡可以透明地調用目标接口。
- 複用了現存的類,程式員不需要修改原有代碼而重用現有的适配者類。
- 将目标類和适配者類解耦,解決了目标類和适配者類接口不一緻的問題。
缺點
對類擴充卡來說,更換擴充卡的實作過程比較複雜。
應用場景
- 以前開發的系統存在滿足新系統功能需求的類,但其接口同新系統的接口不一緻。
- 使用第三方提供的元件,但元件接口定義和自己要求的接口定義不同。
橋接模式
定義
橋接(Bridge)模式的定義如下:将抽象與實作分離,使它們可以獨立變化。它是用組合關系代替繼承關系來實作,進而降低了抽象和實作這兩個可變次元的耦合度。
優點
由于抽象與實作分離,是以擴充能力強;
其實作細節對客戶透明。
缺點
由于聚合關系建立在抽象層,要求開發者針對抽象化進行設計與程式設計,這增加了系統的了解與設計難度。
應用場景
- 當一個類存在兩個獨立變化的次元,且這兩個次元都需要進行擴充時。
- 當一個系統不希望使用繼承或因為多層次繼承導緻系統類的個數急劇增加時。
- 當一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性時。
裝飾模式
定義
裝飾(Decorator)模式的定義:指在不改變現有對象結構的情況下,動态地給該對象增加一些職責(即增加其額外功能)的模式,它屬于對象結構型模式。
優點
采用裝飾模式擴充對象的功能比采用繼承方式更加靈活。
可以設計出多個不同的具體裝飾類,創造出多個不同行為的組合。
缺點
裝飾模式增加了許多子類,如果過度使用會使程式變得很複雜。
應用場景
- 當需要給一個現有類添加附加職責,而又不能采用生成子類的方法進行擴充時。例如,該類被隐藏或者該類是終極類或者采用繼承方式會産生大量的子類。
- 當需要通過對現有的一組基本功能進行排列組合而産生非常多的功能時,采用繼承關系很難實作,而采用裝飾模式卻很好實作。
- 當對象的功能要求可以動态地添加,也可以再動态地撤銷時。
外觀模式
定義
外觀(Facade)模式的定義:是一種通過為多個複雜的子系統提供一個一緻的接口,而使這些子系統更加容易被通路的模式。該模式對外有一個統一接口,外部應用程式不用關心内部子系統的具體的細節,這樣會大大降低應用程式的複雜度,提高了程式的可維護性。
優點
- 降低了子系統與用戶端之間的耦合度,使得子系統的變化不會影響調用它的客戶類。
- 對客戶屏蔽了子系統元件,減少了客戶處理的對象數目,并使得子系統使用起來更加容易。
- 降低了大型軟體系統中的編譯依賴性,簡化了系統在不同平台之間的移植過程,因為編譯一個子系統不會影響其他的子系統,也不會影響外觀對象。
缺點
- 不能很好地限制客戶使用子系統類。
- 增加新的子系統可能需要修改外觀類或用戶端的源代碼,違背了“開閉原則”。
應用場景
- 對分層結構系統建構時,使用外觀模式定義子系統中每層的入口點可以簡化子系統之間的依賴關系。
- 當一個複雜系統的子系統很多時,外觀模式可以為系統設計一個簡單的接口供外界通路。
- 當用戶端與多個子系統之間存在很大的聯系時,引入外觀模式可将它們分離,進而提高子系統的獨立性和可移植性。
享元模式
定義
享元(Flyweight)模式的定義:運用共享技術來有効地支援大量細粒度對象的複用。它通過共享已經存在的又橡來大幅度減少需要建立的對象數量、避免大量相似類的開銷,進而提高系統資源的使用率。
優點
相同對象隻要儲存一份,這降低了系統中對象的數量,進而降低了系統中細粒度對象給記憶體帶來的壓力。
缺點
為了使對象可以共享,需要将一些不能共享的狀态外部化,這将增加程式的複雜性。
讀取享元模式的外部狀态會使得運作時間稍微變長。
應用場景
- 系統中存在大量相同或相似的對象,這些對象耗費大量的記憶體資源。
- 大部分的對象可以按照内部狀态進行分組,且可将不同部分外部化,這樣每一個組隻需儲存一個内部狀态。
- 由于享元模式需要額外維護一個儲存享元的資料結構,是以應當在有足夠多的享元執行個體時才值得使用享元模式。
組合模式
定義
組合(Composite)模式的定義:有時又叫作部分-整體模式,它是一種将對象組合成樹狀的層次結構的模式,用來表示“部分-整體”的關系,使使用者對單個對象群組合對象具有一緻的通路性。
優點
組合模式使得用戶端代碼可以一緻地處理單個對象群組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了用戶端代碼;
更容易在組合體内加入新的對象,用戶端不會因為加入了新的對象而更改源代碼,滿足“開閉原則”;
缺點
設計較複雜,用戶端需要花更多時間理清類之間的層次關系;
不容易限制容器中的構件;
不容易用繼承的方法來增加構件的新功能;
應用場景
在需要表示一個對象整體與部分的層次結構的場合。
要求對使用者隐藏組合對象與單個對象的不同,使用者可以用統一的接口使用組合結構中的所有對象的場合。
模闆方法模式
定義
模闆方法(Template Method)模式的定義如下:定義一個操作中的算法骨架,而将算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行為型模式。
優點
- 它封裝了不變部分,擴充可變部分。它把認為是不變部分的算法封裝到父類中實作,而把可變部分算法由子類繼承實作,便于子類繼續擴充。
- 它在父類中提取了公共的部分代碼,便于代碼複用。
- 部分方法是由子類實作的,是以子類可以通過擴充方式增加相應的功能,符合開閉原則。
缺點
- 對每個不同的實作都需要定義一個子類,這會導緻類的個數增加,系統更加龐大,設計也更加抽象。
- 父類中的抽象方法由子類實作,子類執行的結果會影響父類的結果,這導緻一種反向的控制結構,它提高了代碼閱讀的難度。
應用場景
- 算法的整體步驟很固定,但其中個别部分易變時,這時候可以使用模闆方法模式,将容易變的部分抽象出來,供子類實作。
- 當多個子類存在公共的行為時,可以将其提取出來并集中到一個公共父類中以避免代碼重複。首先,要識别現有代碼中的不同之處,并且将不同之處分離為新的操作。最後,用一個調用這些新的操作的模闆方法來替換這些不同的代碼。
- 當需要控制子類的擴充時,模闆方法隻在特定點調用鈎子操作,這樣就隻允許在這些點進行擴充。
政策模式
定義
政策(Strategy)模式的定義:該模式定義了一系列算法,并将每個算法封裝起來,使它們可以互相替換,且算法的變化不會影響使用算法的客戶。政策模式屬于對象行為模式,它通過對算法進行封裝,把使用算法的責任和算法的實作分割開來,并委派給不同的對象對這些算法進行管理。
優點
- 多重條件語句不易維護,而使用政策模式可以避免使用多重條件語句。
- 政策模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類裡面,進而避免重複的代碼。
- 政策模式可以提供相同行為的不同實作,客戶可以根據不同時間或空間要求選擇不同的。
- 政策模式提供了對開閉原則的完美支援,可以在不修改原代碼的情況下,靈活增加新算法。
- 政策模式把算法的使用放到環境類中,而算法的實作移到具體政策類中,實作了二者的分離。
缺點
- 用戶端必須了解所有政策算法的差別,以便适時選擇恰當的算法類。
- 政策模式造成很多的政策類。
應用場景
- 一個系統需要動态地在幾種算法中選擇一種時,可将每個算法封裝到政策類中。
- 一個類定義了多種行為,并且這些行為在這個類的操作中以多個條件語句的形式出現,可将每個條件分支移入它們各自的政策類中以代替這些條件語句。
- 系統中各算法彼此完全獨立,且要求對客戶隐藏具體算法的實作細節時。
- 系統要求使用算法的客戶不應該知道其操作的資料時,可使用政策模式來隐藏與算法相關的資料結構。
- 多個類隻差別在表現行為不同,可以使用政策模式,在運作時動态選擇具體要執行的行為。
指令模式
定義
指令(Command)模式的定義如下:将一個請求封裝為一個對象,使送出請求的責任和執行請求的責任分割開。這樣兩者之間通過指令對象進行溝通,這樣友善将指令對象進行儲存、傳遞、調用、增加與管理。
優點
- 降低系統的耦合度。指令模式能将調用操作的對象與實作該操作的對象解耦。
- 增加或删除指令非常友善。采用指令模式增加與删除指令不會影響其他類,它滿足“開閉原則”,對擴充比較靈活。
- 可以實作宏指令。指令模式可以與組合模式結合,将多個指令裝配成一個組合指令,即宏指令。
- 友善實作 Undo 和 Redo 操作。指令模式可以與後面介紹的備忘錄模式結合,實作指令的撤銷與恢複。
缺點
可能産生大量具體指令類。因為計對每一個具體操作都需要設計一個具體指令類,這将增加系統的複雜性。
應用場景
- 當系統需要将請求調用者與請求接收者解耦時,指令模式使得調用者和接收者不直接互動。
- 當系統需要随機請求指令或經常增加或删除指令時,指令模式比較友善實作這些功能。
- 當系統需要執行一組操作時,指令模式可以定義宏指令來實作該功能。
- 當系統需要支援指令的撤銷(Undo)操作和恢複(Redo)操作時,可以将指令對象存儲起來,采用備忘錄模式來實作。
責任鍊模式
定義
責任鍊(Chain of Responsibility)模式的定義:為了避免請求發送者與多個請求處理者耦合在一起,将所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鍊;當有請求發生時,可将請求沿着這條鍊傳遞,直到有對象處理它為止。
優點
- 降低了對象之間的耦合度。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鍊的結構,發送者和接收者也無須擁有對方的明确資訊。
- 增強了系統的可擴充性。可以根據需要增加新的請求處理類,滿足開閉原則。
- 增強了給對象指派職責的靈活性。當工作流程發生變化,可以動态地改變鍊内的成員或者調動它們的次序,也可動态地新增或者删除責任。
- 責任鍊簡化了對象之間的連接配接。每個對象隻需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用衆多的 if 或者 if···else 語句。
- 責任分擔。每個類隻需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明确各類的責任範圍,符合類的單一職責原則。
缺點
- 不能保證每個請求一定被處理。由于一個請求沒有明确的接收者,是以不能保證它一定會被處理,該請求可能一直傳到鍊的末端都得不到處理。
- 對比較長的職責鍊,請求的處理可能涉及多個處理對象,系統性能将受到一定影響。
- 職責鍊建立的合理性要靠用戶端來保證,增加了用戶端的複雜性,可能會由于職責鍊的錯誤設定而導緻系統出錯,如可能會造成循環調用。
應用場景
- 有多個對象可以處理一個請求,哪個對象處理該請求由運作時刻自動确定。
- 可動态指定一組對象處理請求,或添加新的處理者。
- 在不明确指定請求處理者的情況下,向多個處理者中的一個送出請求。
狀态模式
定義
狀态(State)模式的定義:對有狀态的對象,把複雜的“判斷邏輯”提取到不同的狀态對象中,允許狀态對象在其内部狀态發生改變時改變其行為。
優點
- 狀态模式将與特定狀态相關的行為局部化到一個狀态中,并且将不同狀态的行為分割開來,滿足“單一職責原則”。
- 減少對象間的互相依賴。将不同的狀态引入獨立的對象中會使得狀态轉換變得更加明确,且減少對象間的互相依賴。
- 有利于程式的擴充。通過定義新的子類很容易地增加新的狀态和轉換。
缺點
- 狀态模式的使用必然會增加系統的類與對象的個數。
- 狀态模式的結構與實作都較為複雜,如果使用不當會導緻程式結構和代碼的混亂。
應用場景
- 當一個對象的行為取決于它的狀态,并且它必須在運作時根據狀态改變它的行為時,就可以考慮使用狀态模式。
- 一個操作中含有龐大的分支結構,并且這些分支決定于對象的狀态時。
觀察者模式
定義
觀察者(Observer)模式的定義:指多個對象間存在一對多的依賴關系,當一個對象的狀态發生改變時,所有依賴于它的對象都得到通知并被自動更新。這種模式有時又稱作釋出-訂閱模式、模型-視圖模式,它是對象行為型模式。
優點
- 降低了目标與觀察者之間的耦合關系,兩者之間是抽象耦合關系。
- 目标與觀察者之間建立了一套觸發機制。
缺點
- 目标與觀察者之間的依賴關系并沒有完全解除,而且有可能出現循環引用。
- 當觀察者對象很多時,通知的釋出會花費很多時間,影響程式的效率。
應用場景
- 對象間存在一對多關系,一個對象的狀态發生改變會影響其他對象。
- 當一個抽象模型有兩個方面,其中一個方面依賴于另一方面時,可将這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。
中介者模式
定義
中介者(Mediator)模式的定義:定義一個中介對象來封裝一系列對象之間的互動,使原有對象之間的耦合松散,且可以獨立地改變它們之間的互動。中介者模式又叫調停模式,它是迪米特法則的典型應用。
優點
- 降低了對象之間的耦合性,使得對象易于獨立地被複用。
- 将對象間的一對多關聯轉變為一對一的關聯,提高系統的靈活性,使得系統易于維護和擴充。
缺點
當同僚類太多時,中介者的職責将很大,它會變得複雜而龐大,以至于系統難以維護。
應用場景
- 當對象之間存在複雜的網狀結構關系而導緻依賴關系混亂且難以複用時。
- 當想建立一個運作于多個類之間的對象,又不想生成新的子類時。
疊代器模式
定義
疊代器(Iterator)模式的定義:提供一個對象來順序通路聚合對象中的一系列資料,而不暴露聚合對象的内部表示。疊代器模式是一種對象行為型模式。
優點
- 通路一個聚合對象的内容而無須暴露它的内部表示。
- 周遊任務交由疊代器完成,這簡化了聚合類。
- 它支援以不同方式周遊一個聚合,甚至可以自定義疊代器的子類以支援新的周遊。
-
增加新的聚合類和疊代器類都很友善,無須修改原有代碼。
封裝性良好,為周遊不同的聚合結構提供一個統一的接口。
缺點
增加了類的個數,這在一定程度上增加了系統的複雜性。
應用場景
- 當需要為聚合對象提供多種周遊方式時。
- 當需要為周遊不同的聚合結構提供一個統一的接口時。
- 當通路一個聚合對象的内容而無須暴露其内部細節的表示時。
通路者模式
定義
通路者(Visitor)模式的定義:将作用于某種資料結構中的各元素的操作分離出來封裝成獨立的類,使其在不改變資料結構的前提下可以添加作用于這些元素的新的操作,為資料結構中的每個元素提供多種通路方式。它将對資料的操作與資料結構進行分離,是行為類模式中最複雜的一種模式。
優點
- 擴充性好。能夠在不修改對象結構中的元素的情況下,為對象結構中的元素添加新的功能。
- 複用性好。可以通過通路者來定義整個對象結構通用的功能,進而提高系統的複用程度。
- 靈活性好。通路者模式将資料結構與作用于結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統的資料結構。
- 符合單一職責原則。通路者模式把相關的行為封裝在一起,構成一個通路者,使每一個通路者的功能都比較單一。
缺點
- 增加新的元素類很困難。在通路者模式中,每增加一個新的元素類,都要在每一個具體通路者類中增加相應的具體操作,這違背了“開閉原則”。
- 破壞封裝。通路者模式中具體元素對通路者公布細節,這破壞了對象的封裝性。
- 違反了依賴倒置原則。通路者模式依賴了具體類,而沒有依賴抽象類。
應用場景
- 對象結構相對穩定,但其操作算法經常變化的程式。
- 對象結構中的對象需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響對象的結構。
- 對象結構包含很多類型的對象,希望對這些對象實施一些依賴于其具體類型的操作。
備忘錄模式
定義
備忘錄(Memento)模式的定義:在不破壞封裝性的前提下,捕獲一個對象的内部狀态,并在該對象之外儲存這個狀态,以便以後當需要時能将該對象恢複到原先儲存的狀态。該模式又叫快照模式。
優點
- 提供了一種可以恢複狀态的機制。當使用者需要時能夠比較友善地将資料恢複到某個曆史的狀态。
- 實作了内部狀态的封裝。除了建立它的發起人之外,其他對象都不能夠通路這些狀态資訊。
- 簡化了發起人類。發起人不需要管理和儲存其内部狀态的各個備份,所有狀态資訊都儲存在備忘錄中,并由管理者進行管理,這符合單一職責原則。
缺點
資源消耗大。如果要儲存的内部狀态資訊過多或者特别頻繁,将會占用比較大的記憶體資源。
應用場景
- 需要儲存與恢複資料的場景,如玩遊戲時的中間結果的存檔功能。
- 需要提供一個可復原操作的場景,如 Word、記事本、Photoshop,Eclipse 等軟體在編輯時按 Ctrl+Z 組合鍵,還有資料庫中事務操作。
解釋器模式
定義
解釋器(Interpreter)模式的定義:給分析對象定義一個語言,并定義該語言的文法表示,再設計一個解析器來解釋語言中的句子。也就是說,用編譯語言的方式來分析應用中的執行個體。這種模式實作了文法表達式處理的接口,該接口解釋一個特定的上下文。
這裡提到的文法和句子的概念同編譯原理中的描述相同,“文法”指語言的文法規則,而“句子”是語言集中的元素。例如,漢語中的句子有很多,“我是中國人”是其中的一個句子,可以用一棵文法樹來直覺地描述語言中的句子。
優點
- 擴充性好。由于在解釋器模式中使用類來表示語言的文法規則,是以可以通過繼承等機制來改變或擴充文法。
- 容易實作。在文法樹中的每個表達式節點類都是相似的,是以實作其文法較為容易。
缺點
- 執行效率較低。解釋器模式中通常使用大量的循環和遞歸調用,當要解釋的句子較複雜時,其運作速度很慢,且代碼的調試過程也比較麻煩。
- 會引起類膨脹。解釋器模式中的每條規則至少需要定義一個類,當包含的文法規則很多時,類的個數将急劇增加,導緻系統難以管理與維護。
- 可應用的場景比較少。在軟體開發中,需要定義語言文法的應用執行個體非常少,是以這種模式很少被使用到。
應用場景
- 當語言的文法較為簡單,且執行效率不是關鍵問題時。
- 當問題重複出現,且可以用一種簡單的語言來進行表達時。
- 當一個語言需要解釋執行,并且語言中的句子可以表示為一個抽象文法樹的時候,如 XML 文檔解釋。
問題場景分析
場景一 - 模拟美國總統
場景
在每一個任期内,美國總統有且僅有一人,假設現在想要模拟産生當今的美國總統對象。
分析
問題的“穩定”是在每一屆任期内,美國的總統隻有一人,“變化”是這個人可以是不同的人。本執行個體适合用單例模式實作,因為單例模式中一個類隻有一個執行個體。下圖是用懶漢式單例實作的結構圖。
Java程式代碼
public class SingletonLazy
{
public static void main(String[] args)
{
President zt1=President.getInstance();
zt1.getName(); //輸出總統的名字
President zt2=President.getInstance();
zt2.getName(); //輸出總統的名字
if(zt1==zt2)
{
System.out.println("他們是同一人!");
}
else
{
System.out.println("他們不是同一人!");
}
}
}
class President
{
//保證instance在所有線程中同步
private static volatile President instance=null;
//private避免類在外部被執行個體化
private President()
{
System.out.println("産生一個總統!");
}
public static synchronized President getInstance()
{
//在getInstance方法上加同步
if(instance==null)
{
instance=new President();
}
else
{
System.out.println("已經有一個總統,不能産生新總統!");
}
return instance;
}
public void getName()
{
System.out.println("我是美國總統:特朗普。");
}
}
程式運作結果
産生一個總統!
我是美國總統:特朗普。
已經有一個總統,不能産生新總統!
我是美國總統:特朗普。
他們是同一人!
場景二 - 畜牧場
場景
設計比較完整的畜牧場體系。
分析
有很多種類的畜牧場,如養馬場用于養馬,養牛場用于養牛。
“穩定”是都是畜牧場,“變化”是畜牧場的類型不一樣,對應的養殖方式也不同。是以該執行個體用工廠方法模式比較适合。
對養馬場和養牛場等具體工廠類,隻要定義一個生成動物的方法 newAnimal() 即可。由于要顯示馬類和牛類等具體産品類的圖像,是以它們的構造函數中用到了 JPanel、JLabd 和 ImageIcon 等元件,并定義一個 show() 方法來顯示它們。
用戶端程式通過對象生成器類 ReadXML2 讀取 XML 配置檔案中的資料來決定養馬還是養牛。其類圖如下圖所示。
Java代碼
package FactoryMethod;
public class AnimalFarmTest
{
public static void main(String[] args)
{
try
{
Animal a;
AnimalFarm af;
af=(AnimalFarm) ReadXML2.getObject();
a=af.newAnimal();
a.show();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
//抽象産品:動物類
interface Animal
{
public void show();
}
//具體産品:馬類
class Horse implements Animal
{
JScrollPane sp;
JFrame jf=new JFrame("工廠方法模式測試");
public Horse()
{
}
public void show()
{
jf.setVisible(true);
}
}
//具體産品:牛類
class Cattle implements Animal
{
JScrollPane sp;
JFrame jf=new JFrame("工廠方法模式測試");
public Cattle()
{
}
public void show()
{
jf.setVisible(true);
}
}
//抽象工廠:畜牧場
interface AnimalFarm
{
public Animal newAnimal();
}
//具體工廠:養馬場
class HorseFarm implements AnimalFarm
{
public Animal newAnimal()
{
System.out.println("新馬出生!");
return new Horse();
}
}
//具體工廠:養牛場
class CattleFarm implements AnimalFarm
{
public Animal newAnimal()
{
System.out.println("新牛出生!");
return new Cattle();
}
}
場景三 - 五子棋
場景
設計五子棋。
分析
五子棋同圍棋一樣,包含多個“黑”或“白”顔色的棋子。
“穩定”是都是棋子,都需要遵循相同的規則,“變化”是顔色不同,位置不同。是以用享元模式比較好。
棋子(ChessPieces)類是抽象享元角色,它包含了一個落子的 DownPieces(Graphics g,Point pt) 方法;白子(WhitePieces)和黑子(BlackPieces)類是具體享元角色,它實作了落子方法;Point 是非享元角色,它指定了落子的位置;WeiqiFactory 是享元工廠角色,它通過 ArrayList 來管理棋子,并且提供了擷取白子或者黑子的 getChessPieces(String type) 方法;客戶類(Chessboard)利用 Graphics 元件在架構窗體中繪制一個棋盤,并實作 mouseClicked(MouseEvent e) 事件處理方法,該方法根據使用者的選擇從享元工廠中擷取白子或者黑子并落在棋盤上。下圖為其類圖。
Java代碼
package flyweight;
public class WzqGame
{
public static void main(String[] args)
{
new Chessboard();
}
}
//棋盤
class Chessboard extends MouseAdapter
{
WeiqiFactory wf;
JFrame f;
Graphics g;
JRadioButton wz;
JRadioButton bz;
Chessboard()
{
}
public void mouseClicked(MouseEvent e)
{
}
}
//抽象享元角色:棋子
interface ChessPieces
{
public void DownPieces(Graphics g,Point pt); //下子
}
//具體享元角色:白子
class WhitePieces implements ChessPieces
{
public void DownPieces(Graphics g,Point pt)
{
g.setColor(Color.WHITE);
g.fillOval(pt.x,pt.y,30,30);
}
}
//具體享元角色:黑子
class BlackPieces implements ChessPieces
{
public void DownPieces(Graphics g,Point pt)
{
g.setColor(Color.BLACK);
g.fillOval(pt.x,pt.y,30,30);
}
}
//享元工廠角色
class WeiqiFactory
{
private ArrayList<ChessPieces> qz;
public WeiqiFactory()
{
}
public ChessPieces getChessPieces(String type)
{
}
}
場景四 - 學生成績狀态轉換
場景
學生成績的狀态轉換。
分析
成績狀态包含了“不及格”“中等”和“優秀” 3 種狀态,當學生的分數小于 60 分時為“不及格”狀态,當分數大于等于 60 分且小于 90 分時為“中等”狀态,當分數大于等于 90 分時為“優秀”狀态。
“穩定”是每個學生都有自己的成績,“變化”是不同的成績對應相應的狀态。是以應該用狀态模式來實作這個程式。
首先,定義一個抽象狀态類(AbstractState),其中包含了環境屬性、狀态名屬性和目前分數屬性,以及加減分方法 addScore(intx) 和檢查目前狀态的抽象方法 checkState();然後,定義“不及格”狀态類 LowState、“中等”狀态類 MiddleState 和“優秀”狀态類 HighState,它們是具體狀态類,實作 checkState() 方法,負責檢査自己的狀态,并根據情況轉換;最後,定義環境類(ScoreContext),其中包含了目前狀态對象和加減分的方法 add(int score),客戶類通過該方法來改變成績狀态。下圖為其類圖。
Java代碼
package state;
public class ScoreStateTest
{
public static void main(String[] args)
{
ScoreContext account=new ScoreContext();
System.out.println("學生成績狀态測試:");
account.add(30);
account.add(40);
account.add(25);
account.add(-15);
account.add(-25);
}
}
//環境類
class ScoreContext
{
private AbstractState state;
ScoreContext()
{
state=new LowState(this);
}
public void setState(AbstractState state)
{
this.state=state;
}
public AbstractState getState()
{
return state;
}
public void add(int score)
{
state.addScore(score);
}
}
//抽象狀态類
abstract class AbstractState
{
protected ScoreContext hj; //環境
protected String stateName; //狀态名
protected int score; //分數
public abstract void checkState(); //檢查目前狀态
public void addScore(int x)
{
score+=x;
System.out.print("加上:"+x+"分,\t目前分數:"+score );
checkState();
System.out.println("分,\t目前狀态:"+hj.getState().stateName);
}
}
//具體狀态類:不及格
class LowState extends AbstractState
{
public LowState(ScoreContext h)
{
hj=h;
stateName="不及格";
score=0;
}
public LowState(AbstractState state)
{
hj=state.hj;
stateName="不及格";
score=state.score;
}
public void checkState()
{
}
}
//具體狀态類:中等
class MiddleState extends AbstractState
{
public MiddleState(AbstractState state)
{
hj=state.hj;
stateName="中等";
score=state.score;
}
public void checkState()
{
}
}
//具體狀态類:優秀
class HighState extends AbstractState
{
public HighState(AbstractState state)
{
hj=state.hj;
stateName="優秀";
score=state.score;
}
public void checkState()
{
}
}
場景五 - 皮包選購
場景
模拟女士皮包的選購。
分析
女士皮包有很多種,可以按用途分、按皮質分、按品牌分、按顔色分、按大小分等,存在多個次元的變化。
“穩定”是都是皮包,“變化”有多個次元,皮質不同,品牌不同,顔色不同,等等。是以采用橋接模式來實作女士皮包的選購比較合适。
本執行個體按用途分可選錢包(Wallet)和挎包(HandBag),按顔色分可選黃色(Yellow)和紅色(Red)。可以按兩個次元定義為顔色類和包類。(點此下載下傳本執行個體所要顯示的包的圖檔)。
顔色類(Color)是一個次元,定義為實作化角色,它有兩個具體實作化角色:黃色和紅色,通過 getColor() 方法可以選擇顔色;包類(Bag)是另一個次元,定義為抽象化角色,它有兩個擴充抽象化角色:挎包和錢包,它包含了顔色類對象,通過 getName() 方法可以選擇相關顔色的挎包和錢包。
客戶類通過 ReadXML 類從 XML 配置檔案中擷取包資訊(點此下載下傳 XML 配置檔案),并把選到的産品通過窗體顯示出現。下圖為類圖。
Java代碼
package bridge;
import java.awt.*;
import javax.swing.*;
public class BagManage
{
public static void main(String[] args)
{
Color color;
Bag bag;
color=(Color)ReadXML.getObject("color");
bag=(Bag)ReadXML.getObject("bag");
bag.setColor(color);
String name=bag.getName();
show(name);
}
}
//實作化角色:顔色
interface Color
{
String getColor();
}
//具體實作化角色:黃色
class Yellow implements Color
{
public String getColor()
{
return "yellow";
}
}
//具體實作化角色:紅色
class Red implements Color
{
public String getColor()
{
return "red";
}
}
//抽象化角色:包
abstract class Bag
{
protected Color color;
public void setColor(Color color)
{
this.color=color;
}
public abstract String getName();
}
//擴充抽象化角色:挎包
class HandBag extends Bag
{
public String getName()
{
return color.getColor()+"HandBag";
}
}
//擴充抽象化角色:錢包
class Wallet extends Bag
{
public String getName()
{
return color.getColor()+"Wallet";
}
}
場景六 - 汽車發動機
場景
模拟新能源汽車的發動機。
分析
新能源汽車的發動機有電能發動機(Electric Motor)和光能發動機(Optical Motor)等,各種發動機的驅動方法不同,例如,電能發動機的驅動方法 electricDrive() 是用電能驅動,而光能發動機的驅動方法 opticalDrive() 是用光能驅動,它們是擴充卡模式中被通路的适配者。
“穩定”是都是汽車發動機,“變化”是驅動方法不同,電能發動機隻能靠電,而光能發動機隻能靠光來發動。是以用擴充卡模式比較合适。
用戶端希望用統一的發動機驅動方法 drive() 通路這兩種發動機,是以必須定義一個統一的目标接口 Motor,然後再定義電能擴充卡(Electric Adapter)和光能擴充卡(Optical Adapter)去适配這兩種發動機。
我們把用戶端想通路的新能源發動機的擴充卡的名稱放在 XML 配置檔案中(點此下載下傳 XML 檔案),用戶端可以通過對象生成器類 ReadXML 去讀取。這樣,用戶端就可以通過 Motor 接口随便使用任意一種新能源發動機去驅動汽車,下圖為其類圖。
Java代碼
package adapter;
//目标:發動機
interface Motor
{
public void drive();
}
//适配者1:電能發動機
class ElectricMotor
{
public void electricDrive()
{
System.out.println("電能發動機驅動汽車!");
}
}
//适配者2:光能發動機
class OpticalMotor
{
public void opticalDrive()
{
System.out.println("光能發動機驅動汽車!");
}
}
//電能擴充卡
class ElectricAdapter implements Motor
{
private ElectricMotor emotor;
public ElectricAdapter()
{
emotor=new ElectricMotor();
}
public void drive()
{
emotor.electricDrive();
}
}
//光能擴充卡
class OpticalAdapter implements Motor
{
private OpticalMotor omotor;
public OpticalAdapter()
{
omotor=new OpticalMotor();
}
public void drive()
{
omotor.opticalDrive();
}
}
專業術語了解
複用
傳統的複用
- 代碼的剪貼複用:最初步的複用,每塊代碼分散在各處,獨立的演變,仍需要針對每一塊修改和測試。這種複用帶來的好處很有限。
- 算法的複用:在已有的成熟算法中選擇一個比自己重新開發一個好的多。比如排序算法。這就是算法的複用。
- 資料結構的複用:比如隊列、棧、連結清單,在實踐中得到了大量的應用。
傳統複用的缺陷
傳統複用方案的緻命缺陷是複用常常以破壞可維護性為代價的
面向對象設計的複用
在面向對象語言中,語言具有的良好特性,使得細節的複用變得簡單成熟而不再是重點。複用的重點在于帶有商業邏輯的抽象層次上。這是提高複用性同時保持和提高可維護性的關鍵。
抽象層次應是較為穩定的,是複用的重點。抽象層次的子產品相對獨立于具體層次。這樣具體層次内部的變化就不會影響到抽象層次,抽象層次的子產品複用會較為容易。
在面向對象設計中,可維護性複用以設計原則和設計模式為基礎。
複用的好處
- 較高的生産效率。(以及随之而來的成本降低)
- 較高的軟體品質。(錯誤可以更快的被糾正)
- 恰當的使用複用可以改善系統的可維護性。
總結
一個功能有多個實作,會引發同一問題多處修改而維護難的問題。
這種情況下,可觀察多個實作的共性以及個性,将共性封裝成方法,個性則以傳參的方式來适配不同業務的需求。進而提高了功能的内聚性,降低與業務代碼之間的耦合性,履行封裝的單一責任原則。
通俗的講,當經常使用某一功能時,把一個功能寫成一個子產品,以便當再次需要相同功能的時候,可以直接使用,而不用重新開發。
穩定與變化
在設計模式中,從字面意思了解“穩定與變化”,穩定就是不變的部分,變化就是會發生改變的部分,而二者是互相統一,缺一不可的。具體一點,穩定就是指某個功能的共性,公有的部分,也就是可以封裝成方法或子產品的部分,而變化就是指相應功能會變化的部分,也就是封裝成的方法或子產品的傳入參數或接口。進而達到“高内聚,低耦合”。
舉一個例子,計算各各種圖形的面積時,“穩定”指都有圖像類型和圖形資訊的傳入,最後都是輸入一個值(即面積),“變化”指圖形的類型不同,對應的傳入圖形資訊的數量不同,同時對應的求面積的方法也不同。使用可以将計算面積封裝成一個方法(穩定),該方法根據輸入參數中圖形類型的不同來調用相應的求面積的方法(變化)。
編譯時依賴與運作時依賴
編譯時依賴指編譯時要依賴的庫檔案等可以讓執行檔案運作的環境,運作時依賴指運作時要依賴的庫檔案等可以讓執行檔案運作的環境,一般來說,編譯時依賴為動态連結,産生的執行檔案不能直接在其他電腦上運作,而運作時依賴為靜态連結,所依賴的庫檔案已經打包到執行檔案中,在其他電腦上也可以運作。
舉一個例子,在Visual Studio的MFC中,編譯時依賴為debug模式,配置屬性為在動态庫中使用MFC,多線程調試DLL(/MDd)。運作時依賴為release模式,配置屬性為在靜态庫中使用MFC,多線程(/MT)。
類繼承與對象組合
繼承
繼承是Is a 的關系,比如說Student繼承Person,則說明Student is a Person。繼承的優點是子類可以重寫父類的方法來友善地實作對父類的擴充。
繼承的缺點
- 父類的内部細節對子類是可見的。
- 子類從父類繼承的方法在編譯時就确定下來了,是以無法在運作期間改變從父類繼承的方法的行為。
- 如果對父類的方法做了修改的話(比如增加了一個參數),則子類的方法必須做出相應的修改。是以說子類與父類是一種高耦合,違背了面向對象思想。
組合
組合也就是設計類的時候把要組合的類的對象加入到該類中作為自己的成員變量。
組合的優點
- 目前對象隻能通過所包含的那個對象去調用其方法,是以所包含的對象的内部細節對目前對象時不可見的。
- 目前對象與包含的對象是一個低耦合關系,如果修改包含對象的類中代碼不需要修改目前對象類的代碼。
- 目前對象可以在運作時動态的綁定所包含的對象。可以通過set方法給所包含對象指派。
組合的缺點
- 容易産生過多的對象。
- 為了能組合多個對象,必須仔細對接口進行定義。
總結
由此可見,組合比繼承更具靈活性和穩定性,是以在設計的時候優先使用組合。隻有當下列條件滿足時才考慮使用繼承:
- 子類是一種特殊的類型,而不隻是父類的一個角色
- 子類的執行個體不需要變成另一個類的對象
- 子類擴充,而不是覆寫或者使父類的功能失效
依賴倒置原則
依賴倒置原則的原始定義為:高層子產品不應該依賴低層子產品,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。其核心思想是:要面向接口程式設計,不要面向實作程式設計。
依賴倒置原則是實作開閉原則的重要途徑之一,它降低了客戶與實作子產品之間的耦合。
由于在軟體設計中,細節具有多變性,而抽象層則相對穩定,是以以抽象為基礎搭建起來的架構要比以細節為基礎搭建起來的架構要穩定得多。這裡的抽象指的是接口或者抽象類,而細節是指具體的實作類。
使用接口或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操作,把展現細節的任務交給它們的實作類去完成。
作用
- 依賴倒置原則可以降低類間的耦合性。
- 依賴倒置原則可以提高系統的穩定性。
- 依賴倒置原則可以減少并行開發引起的風險。
- 依賴倒置原則可以提高代碼的可讀性和可維護性。
面向接口程式設計
定義
在一個面向對象的系統中,系統的各種功能是由許許多多的不同對象協作完成的。在這種情況下,各個對象内部是如何實作自己的,對系統設計人員來講就不那麼重要了;而各個對象之間的協作關系則成為系統設計的關鍵。小到不同類之間的通信,大到各子產品之間的互動,在系統設計之初都是要着重考慮的,這也是系統設計的主要工作内容。面向接口程式設計就是指按照這種思想來程式設計。
面向interface程式設計,原意是指面向抽象協定程式設計,實作者在實作時要嚴格按協定來辦。面向對象程式設計是指面向抽象和具象。抽象和具象是沖突的統一體,不可能隻有抽象沒有具象。一般懂得抽象的人都明白這個道理。 但有的人隻知具象卻不知抽象為何物。
對接口的了解
接口從更深層次的了解,應是定義(規範,限制)與實作(名實分離的原則)的分離。
接口的本身反映了系統設計人員對系統的抽象了解。
接口應有兩類:
第一類是對一個體的抽象,它可對應為一個抽象體(abstract class);
第二類是對一個體某一方面的抽象,即形成一個抽象面(interface);
一個體有可能有多個抽象面,抽象體與抽象面是有差別的。