java设计模式(1)
1、設計模式概述
1.1 介紹
本教程來自http://c.biancheng.net/view/1317.html
目的是為了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。
設計模式的本質是面向對象設計原則的實際運用,是對類的封裝性、繼承性和多態性以及類的關聯關系和組合關系的充分理解。正確使用設計模式具有以下優點:
- 可以提高程序員的思維能力、編程能力和設計能力。
- 使程序設計更加標準化、代碼編制更加工程化,使軟件開發效率大大提高,從而縮短軟件的開發周期。
- 使設計的代碼可重用性高、可讀性強、可靠性高、靈活性好、可維護性強。
當然,軟件設計模式只是一個引導。在具體的軟件幵發中,必須根據設計的應用系統的特點和要求來恰當選擇。對于簡單的程序開發,可能寫一個簡單的算法要比引入某種設計模式更加容易。但對大項目的開發或者框架設計,用設計模式來組織代碼顯然更好。
1.2 分類
1.根據目的來分
根據模式是用來完成什么工作來劃分,這種方式可分為創建型模式、結構型模式和行為型模式 3 種。
- 創建型模式:用于描述“怎樣創建對象”,它的主要特點是“將對象的創建與使用分離”。
GoF 中提供了單例、原型、工廠方法、抽象工廠、建造者等 5 種創建型模式。 - 結構型模式:用于描述如何將類或對象按某種布局組成更大的結構。
GoF 中提供了代理、適配器、橋接、裝飾、外觀、享元、組合等 7 種結構型模式。 - 行為型模式:用于描述類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,以及怎樣分配職責。
GoF 中提供了模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄、解釋器等 11 種行為型模式。
2.根據作用范圍來分
根據模式是主要用于類上還是主要用于對象上來分,這種方式可分為類模式和對象模式兩種。
- 類模式:用于處理類與子類之間的關系,這些關系通過繼承來建立,是靜態的,在編譯時刻便確定下來了。
GoF中的工廠方法、(類)適配器、模板方法、解釋器屬于該模式。 - 對象模式:用于處理對象之間的關系,這些關系可以通過組合或聚合來實現,在運行時刻是可以變化的,更具動態性。
GoF 中除了以上 4 種,其他的都是對象模式。
1.3 簡要介紹23種設計模式的功能
2、UML
UML(Unified Modeling Language,統一建模語言)是用來設計軟件藍圖的可視化建模語言,是一種為面向對象系統的產品進行說明、可視化和編制文檔的標準語言,獨立于任何一種具體的程序設計語言。
2.1 基本構件
在 UML 中,所有的描述由事物、關系和圖這些構件組成。下圖完整地描述了所有構件的關系:
2.1.1 事物
(1) 結構事物
結構事物是模型中的靜態部分,用以呈現概念或實體的表現元素,如下表所示。
(2)行為事物
行為事物指 UML 模型中的動態部分,如下表所示。
(3)分組事物
目前只有一種分組事物,即包。包純碎是概念上的,只存在于開發階段,結構事物、行為事物甚至分組事物都有可能放在一個包中,如下表所示。
(4)注釋事物
注釋事物是解釋 UML 模型元素的部分,如下表所示。
2.1.2 圖
在 UML 2.0 的 13 種圖中,類圖(Class Diagrams)是使用頻率最高的 UML 圖之一。類圖描述系統中的類,以及各個類之間的關系的靜態視圖,能夠讓我們在正確編寫代碼之前對系統有一個全面的認識。類圖是一種模型類型,確切地說,是一種靜態模型類型。類圖表示類、接口和它們之間的協作關系,用于系統設計階段。
類、接口、類圖
類圖中,需注意以下幾點:
- 抽象類或抽象方法用斜體表示
- 如果是接口,則在類名上方加 <>
- 字段和方法返回值的數據類型非必需
- 靜態類或靜態方法加下劃線
2.1.3 關系
UML 將事物之間的聯系歸納為 6 種,并用對應的圖形類表示。下面根據類與類之間的耦合度從弱到強排列:
依賴關系、關聯關系、聚合關系、組合關系、泛化關系和實現關系。其中泛化和實現的耦合度相等,它們是最強的。
總結:
案例:
3、7種設計原則
3.1 開閉原則
定義
軟件實體應當對擴展開放,對修改關閉(Software entities should be open for extension,but closed for modification),這就是開閉原則的經典定義。
這里的軟件實體包括以下幾個部分:
開閉原則的含義是:當應用的需求改變時,在不修改軟件實體的源代碼或者二進制代碼的前提下,可以擴展模塊的功能,使其滿足新的需求。
作用
開閉原則是面向對象程序設計的終極目標,它使軟件實體擁有一定的適應性和靈活性的同時具備穩定性和延續性。具體來說,其作用如下。
軟件遵守開閉原則的話,軟件測試時只需要對擴展的代碼進行測試就可以了,因為原有的測試代碼仍然能夠正常運行。
粒度越小,被復用的可能性就越大;在面向對象的程序設計中,根據原子和抽象編程可以提高代碼的可復用性。
遵守開閉原則的軟件,其穩定性高和延續性強,從而易于擴展和維護。
實現
可以通過“抽象約束、封裝變化”來實現開閉原則,即通過接口或者抽象類為軟件實體定義一個相對穩定的抽象層,而將相同的可變因素封裝在相同的具體實現類中。
因為抽象靈活性好,適應性廣,只要抽象的合理,可以基本保持軟件架構的穩定。而軟件中易變的細節可以從抽象派生來的實現類來進行擴展,當軟件需要發生變化時,只需要根據需求重新派生一個實現類來擴展就可以了。
下面以 Windows 的桌面主題為例介紹開閉原則的應用。
分析:Windows 的主題是桌面背景圖片、窗口顏色和聲音等元素的組合。用戶可以根據自己的喜愛更換自己的桌面主題,也可以從網上下載新的主題。這些主題有共同的特點,可以為其定義一個抽象類(Abstract Subject),而每個具體的主題(Specific Subject)是其子類。用戶窗體可以根據需要選擇或者增加新的主題,而不需要修改原代碼,所以它是滿足開閉原則的,其類圖如圖 1 所示。
3.2 里氏替換
定義
繼承必須確保超類所擁有的性質在子類中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。核心就是子類能完全替換它的基類。
里氏替換原則是繼承與復用的基石,只有當子類可以替換掉基類,且系統的功能不受影響時,基類才能被復用,而子類也能夠在基礎類上增加新的行為。所以里氏替換原則指的是任何基類可以出現的地方,子類一定可以出現。
里氏替換原則是對 “開閉原則” 的補充,實現 “開閉原則” 的關鍵步驟就是抽象化,而基類與子類的繼承關系就是抽象化的具體實現,所以里氏替換原則是對實現抽象化的具體步驟的規范。
作用
里氏替換原則的主要作用如下。
- 里氏替換原則是實現開閉原則的重要方式之一。
- 它克服了繼承中重寫父類造成的可復用性變差的缺點。
- 它是動作正確性的保證。即類的擴展不會給已有的系統引入新的錯誤,降低了代碼出錯的可能性。
- 加強程序的健壯性,同時變更時可以做到非常好的兼容性,提高程序的維護性、可擴展性,降低需求變更時引入的風險。
實現
里氏替換原則通俗來講就是:子類可以擴展父類的功能,但不能改變父類原有的功能。也就是說:子類繼承父類時,除添加新的方法完成新增功能外,盡量不要重寫父類的方法。
根據上述理解,對里氏替換原則的定義可以總結如下:
- 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法
- 子類中可以增加自己特有的方法
- 當子類的方法重載父類的方法時,方法的前置條件(即方法的輸入參數)要比父類的方法更寬松
- 當子類的方法實現父類的方法時(重寫/重載或實現抽象方法),方法的后置條件(即方法的的輸出/返回值)要比父類的方法更嚴格或相等
(上面第3條的解釋參見:https://blog.csdn.net/qq_39552268/article/details/112213037)
通過重寫父類的方法來完成新的功能寫起來雖然簡單,但是整個繼承體系的可復用性會比較差,特別是運用多態比較頻繁時,程序運行出錯的概率會非常大。
如果程序違背了里氏替換原則,則繼承類的對象在基類出現的地方會出現運行錯誤。這時其修正方法是:取消原來的繼承關系,重新設計它們之間的關系。
關于里氏替換原則的例子,最有名的是“正方形不是長方形”。當然,生活中也有很多類似的例子,例如,企鵝、鴕鳥和幾維鳥從生物學的角度來劃分,它們屬于鳥類;但從類的繼承關系來看,由于它們不能繼承“鳥”會飛的功能,所以它們不能定義成“鳥”的子類。同樣,由于“氣球魚”不會游泳,所以不能定義成“魚”的子類;“玩具炮”炸不了敵人,所以不能定義成“炮”的子類等。
下面以“幾維鳥不是鳥”為例來說明里氏替換原則。
【例1】里氏替換原則在“幾維鳥不是鳥”實例中的應用。
分析:鳥一般都會飛行,如燕子的飛行速度大概是每小時 120 千米。但是新西蘭的幾維鳥由于翅膀退化無法飛行。假如要設計一個實例,計算這兩種鳥飛行 300 千米要花費的時間。顯然,拿燕子來測試這段代碼,結果正確,能計算出所需要的時間;但拿幾維鳥來測試,結果會發生“除零異常”或是“無窮大”,明顯不符合預期,其類圖如圖 1 所示。
public class LSPtest {public static void main(String[] args) {Bird bird1 = new Swallow();Bird bird2 = new BrownKiwi();bird1.setSpeed(120);bird2.setSpeed(120);System.out.println("如果飛行300公里:");try {System.out.println("燕子將飛行" + bird1.getFlyTime(300) + "小時.");System.out.println("幾維鳥將飛行" + bird2.getFlyTime(300) + "小時。");} catch (Exception err) {System.out.println("發生錯誤了!");}} } //鳥類 class Bird {double flySpeed;public void setSpeed(double speed) {flySpeed = speed;}public double getFlyTime(double distance) {return (distance / flySpeed);} } //燕子類 class Swallow extends Bird { } //幾維鳥類 class BrownKiwi extends Bird {public void setSpeed(double speed) {flySpeed = 0;} }運行結果:
如果飛行300公里: 燕子將飛行2.5小時. 幾維鳥將飛行Infinity小時。程序運行錯誤的原因是:幾維鳥類重寫了鳥類的 setSpeed(double speed) 方法,這違背了里氏替換原則。正確的做法是:取消幾維鳥原來的繼承關系,定義鳥和幾維鳥的更一般的父類,如動物類,它們都有奔跑的能力。幾維鳥的飛行速度雖然為 0,但奔跑速度不為 0,可以計算出其奔跑 300 千米所要花費的時間。其類圖如圖 2 所示。
3.3 依賴倒置
定義
高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象(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)。其核心思想是:要面向接口編程,不要面向實現編程。
由于在軟件設計中,細節具有多變性,而抽象層則相對穩定,因此以抽象為基礎搭建起來的架構要比以細節為基礎搭建起來的架構要穩定得多。這里的抽象指的是接口或者抽象類,而細節是指具體的實現類。
使用接口或者抽象類的目的是制定好規范和契約,而不去涉及任何具體的操作,把展現細節的任務交給它們的實現類去完成。
作用
依賴倒置原則的主要作用如下。
- 依賴倒置原則可以降低類間的耦合性。
- 依賴倒置原則可以提高系統的穩定性。
- 依賴倒置原則可以減少并行開發引起的風險。
- 依賴倒置原則可以提高代碼的可讀性和可維護性。
實現
依賴倒置原則的目的是通過要面向接口的編程來降低類間的耦合性,所以我們在實際編程中只要遵循以下4點,就能在項目中滿足這個規則。
下面以“顧客購物程序”為例來說明依賴倒置原則的應用。
【例1】依賴倒置原則在“顧客購物程序”中的應用。
分析:本程序反映了 “顧客類”與“商店類”的關系。商店類中有 sell() 方法,顧客類通過該方法購物以下代碼定義了顧客類通過韶關網店 ShaoguanShop 購物:
class Customer {public void shopping(ShaoguanShop shop) {//購物System.out.println(shop.sell());} }但是,這種設計存在缺點,如果該顧客想從另外一家商店(如婺源網店 WuyuanShop)購物,就要將該顧客的代碼修改如下:
class Customer {public void shopping(WuyuanShop shop) {//購物System.out.println(shop.sell());} }顧客每更換一家商店,都要修改一次代碼,這明顯違背了開閉原則。存在以上缺點的原因是:顧客類設計時同具體的商店類綁定了,這違背了依賴倒置原則。解決方法是:定義“婺源網店”和“韶關網店”的共同接口 Shop,顧客類面向該接口編程,其代碼修改如下:
class Customer {public void shopping(Shop shop) {//購物System.out.println(shop.sell());} }這樣,不管顧客類 Customer 訪問什么商店,或者增加新的商店,都不需要修改原有代碼了,其類圖如圖 1 所示。
程序的運行結果如下:
顧客購買以下商品: 韶關土特產:香菇、木耳…… 婺源土特產:綠茶、酒糟魚……3.4 單一職責
定義
又稱單一功能原則。這里的職責是指類變化的原因,單一職責原則規定一個類應該有且僅有一個引起它變化的原因,否則類應該被拆分(There should never be more than one reason for a class to change)。
該原則提出對象不應該承擔太多職責,如果一個對象承擔了太多的職責,至少存在以下兩個缺點:
作用
單一職責原則的核心就是控制類的粒度大小、將對象解耦、提高其內聚性。如果遵循單一職責原則將有以下優點。
- 降低類的復雜度。一個類只負責一項職責,其邏輯肯定要比負責多項職責簡單得多。
- 提高類的可讀性。復雜性降低,自然其可讀性會提高。
- 提高系統的可維護性。可讀性提高,那自然更容易維護了。
- 變更引起的風險降低。變更是必然的,如果單一職責原則遵守得好,當修改一個功能時,可以顯著降低對其他功能的影響。
實現
單一職責原則是最簡單但又最難運用的原則,需要設計人員發現類的不同職責并將其分離,再封裝到不同的類或模塊中。而發現類的多重職責需要設計人員具有較強的分析設計能力和相關重構經驗。
下面以大學學生工作管理程序為例介紹單一職責原則的應用。
分析:大學學生工作主要包括學生生活輔導和學生學業指導兩個方面的工作,其中生活輔導主要包括班委建設、出勤統計、心理輔導、費用催繳、班級管理等工作,學業指導主要包括專業引導、學習輔導、科研指導、學習總結等工作。如果將這些工作交給一位老師負責顯然不合理,正確的做法是生活輔導由輔導員負責,學業指導由學業導師負責,其類圖如圖所示。
注意:單一職責同樣也適用于方法。一個方法應該盡可能做好一件事情。如果一個方法處理的事情太多,其顆粒度會變得很粗,不利于重用。
3.5 接口隔離
定義
要求程序員盡量將臃腫龐大的接口拆分成更小的和更具體的接口,讓接口中只包含客戶感興趣的方法。客戶端不應該被迫依賴于它不使用的方法。該原則還有另外一個定義:一個類對另一個類的依賴應該建立在最小的接口上。
以上兩個定義的含義是:要為各個類建立它們需要的專用接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調用。
接口隔離原則和單一職責都是為了提高類的內聚性、降低它們之間的耦合性,體現了封裝的思想,但兩者是不同的:
- 單一職責原則注重的是職責,而接口隔離原則注重的是對接口依賴的隔離。
- 單一職責原則主要是約束類,它針對的是程序中的實現和細節;接口隔離原則主要約束接口,主要針對抽象和程序整體框架的構建。
作用
接口隔離原則是為了約束接口、降低類對接口的依賴性,遵循接口隔離原則有以下 5 個優點。
實現
在具體應用接口隔離原則時,應該根據以下幾個規則來衡量。
- 接口盡量小,但是要有限度。一個接口只服務于一個子模塊或業務邏輯。
- 為依賴接口的類定制服務。只提供調用者需要的方法,屏蔽不需要的方法。
- 了解環境,拒絕盲從。每個項目或產品都有選定的環境因素,環境不同,接口拆分的標準就不同深入了解業務邏輯。
- 提高內聚,減少對外交互。使接口用最少的方法去完成最多的事情。
下面以學生成績管理程序為例介紹接口隔離原則的應用。
分析:學生成績管理程序一般包含插入成績、刪除成績、修改成績、計算總分、計算均分、打印成績信息、査詢成績信息等功能,如果將這些功能全部放到一個接口中顯然不太合理,正確的做法是將它們分別放在輸入模塊、統計模塊和打印模塊等 3 個模塊中,其類圖如圖 1 所示。
運行結果:
輸入模塊的insert()方法被調用! 統計模塊的countTotalScore()方法被調用! 打印模塊的printStuInfo()方法被調用!3.6 迪米特
定義
又叫作最少知識原則。迪米特法則的定義是:只與你的直接朋友交談,不跟“陌生人”說話。其含義是:如果兩個軟件實體無須直接通信,那么就不應當發生直接的相互調用,可以通過第三方轉發該調用。其目的是降低類之間的耦合度,提高模塊的相對獨立性。
迪米特法則中的“朋友”是指:當前對象本身、當前對象的成員對象、當前對象所創建的對象、當前對象的方法參數等,這些對象同當前對象存在關聯、聚合或組合關系,可以直接訪問這些對象的方法。
作用
迪米特法則要求限制軟件實體之間通信的寬度和深度,正確使用迪米特法則將有以下兩個優點。
- 降低了類之間的耦合度,提高了模塊的相對獨立性。
- 由于親合度降低,從而提高了類的可復用率和系統的擴展性。
但是,過度使用迪米特法則會使系統產生大量的中介類,從而增加系統的復雜性,使模塊之間的通信效率降低。所以,在釆用迪米特法則時需要反復權衡,確保高內聚和低耦合的同時,保證系統的結構清晰。
實現
從迪米特法則的定義和特點可知,它強調以下兩點:
- 從依賴者的角度來說,只依賴應該依賴的對象。
- 從被依賴者的角度說,只暴露應該暴露的方法。
所以,在運用迪米特法則時要注意以下 6 點。
【例1】明星與經紀人的關系實例。
分析:明星由于全身心投入藝術,所以許多日常事務由經紀人負責處理,如與粉絲的見面會,與媒體公司的業務洽淡等。這里的經紀人是明星的朋友,而粉絲和媒體公司是陌生人,所以適合使用迪米特法則,其類圖如圖 1 所示。
運行結果:
粉絲韓丞與明星林心如見面了。 中國傳媒有限公司與明星林心如洽淡業務。3.7 合成復用
定義
又叫組合/聚合復用原則,它要求在軟件復用時,要盡量先使用組合或者聚合等關聯關系來實現,其次才考慮使用繼承關系來實現。
如果要使用繼承關系,則必須嚴格遵循里氏替換原則。合成復用原則同里氏替換原則相輔相成的,兩者都是開閉原則的具體實現規范。
作用
通常類的復用分為繼承復用和合成復用兩種,繼承復用雖然有簡單和易實現的優點,但它也存在以下缺點。
采用組合或聚合復用時,可以將已有對象納入新對象中,使之成為新對象的一部分,新對象可以調用已有對象的功能,它有以下優點。
實現
合成復用原則是通過將已有的對象納入新對象中,作為新對象的成員對象來實現的,新對象可以調用已有對象的功能,從而達到復用。
下面以汽車分類管理程序為例來介紹合成復用原則的應用。
分析:汽車按“動力源”劃分可分為汽油汽車、電動汽車等;按“顏色”劃分可分為白色汽車、黑色汽車和紅色汽車等。如果同時考慮這兩種分類,其組合就很多。圖 1 所示是用繼承關系實現的汽車分類的類圖。
從上圖可以看出用繼承關系實現會產生很多子類,而且增加新的“動力源”或者增加新的“顏色”都要修改源代碼,這違背了開閉原則,顯然不可取。但如果改用組合關系實現就能很好地解決以上問題,其類圖如下圖所示。
3.8 總結
各種原則要求的側重點不同,下面我們分別用一句話歸納總結軟件設計模式的七大原則,如下表所示。
實際上,這些原則的目的只有一個:降低對象之間的耦合,增加程序的可復用性、可擴展性和可維護性。
記憶口訣:訪問加限制,函數要節儉,依賴不允許,動態加接口,父類要抽象,擴展不更改。
在程序設計時,我們應該將程序功能最小化,每個類只干一件事。若有類似功能基礎之上添加新功能,則要合理使用繼承。對于多方法的調用,要會運用接口,同時合理設置接口功能與數量。最后類與類之間做到低耦合高內聚。
4、23中設計模式介紹
開始學習設計模式前,我們先來看看軟件架構的設計過程,及需要達成的目標和盡量避免的陷阱。
代碼復用
無論是開發哪種軟件產品,成本和時間都是最重要的。較少的開發時間意味著可以比競爭對手更早進入市場。較低的開發成本意味著能夠留出更多的營銷資金,覆蓋更廣泛的潛在客戶。
其中,代碼復用是減少開發成本最常用的方式之一,其目的非常明顯,即:與其反復從頭開發,不如在新對象中重用已有的代碼。
這個想法表面看起來很棒,但實際上要讓已有的代碼在全新的代碼中工作,還是需要付出額外努力的。組件間緊密的耦合、對具體類而非接口的依賴和硬編碼的行為都會降低代碼的靈活性,使得復用這些代碼變得更加困難。
使用設計模式是增加軟件組件靈活性并使其易于復用的方式之一。但是,這可能也會讓組件變得更加復雜。
一般情況下,復用可以分為三個層次。在最底層,可以復用類、類庫、容器,也許還有一些類的“團體(例如容器和迭代器)”。
框架位于最高層。它們能幫助你精簡自己的設計,可以明確解決問題所需的抽象概念,然后用類來表示這些概念并定義其關系。例如,JUnit 是一個小型框架,也是框架的“Hello, world”,其中定義了 Test、TestCase 和 TestSuite 這幾個類及其關系。框架通常比單個類的顆粒度要大。你可以通過在某處構建子類來與框架建立聯系。這些子類信奉“別給我們打電話,我們會給你打電話的。”
還有一個中間層次。這是我覺得設計模式所處的位置。設計模式比框架更小且更抽象。它們實際上是對一組類的關系及其互動方式的描述。當你從類轉向模式,并最終到達框架的過程中,復用程度會不斷增加。
中間層次的優點在于模式提供的復用方式要比框架的風險小。創建框架是一項投入重大且風險很高的工作,模式則能讓你獨立于具體代碼來復用設計思想和理念。
擴展性
需求變化是程序員生命中唯一不變的事情。比如以下幾種場景:
- 你在 Windows 平臺上發布了一款游戲,現在人們想要 Mac OS 的版本。
- 你創建了一個使用方形按鈕的 GUI 框架,但幾個月后開始流行原型按鈕。
- 你設計了一款優秀的電子商務網站,但僅僅幾個月后,客戶就要求新增電話訂單的功能。
每個軟件開發者都經歷過許多相似的故事,導致它們發生的原因也不少。
首先,在完成了第一版的程序后,我們就應該做好了從頭開始優化重寫代碼的準備,因為現在你已經能在很多方面更好的理解問題了,同時在專業水平上也有所提高,所以之前的代碼現在看上去可能會顯得很糟糕。
其次,可能是在你掌控之外的某些事情發生了變化,這也是導致許多開發團隊轉變最初想法的原因。比如,每位在網絡應用中使用 Flash 的開發者都必須重新開發或移植代碼,因為不斷地有瀏覽器停止對 Flash 格式地支持。
最后,可能是需求的改變,之前你的客戶對當前版本的程序感到滿意,但是現在希望對程序進行 11 個“小小”的改動,使其可完成原始計劃階段中完全沒有提到的功能,新增或改變功能。
當然這也有好的一面,如果有人要求你對程序進行修改,至少說明還有人關心它。因此在設計程序架構時,有經驗的開發者都會盡量選擇支持未來任何可能變更的方式。
如何正確使用設計模式?
設計模式不是為每個人準備的,而是基于業務來選擇設計模式,需要時就能想到它。要明白一點,技術永遠為業務服務,技術只是滿足業務需要的一個工具。我們需要掌握每種設計模式的應用場景、特征、優缺點,以及每種設計模式的關聯關系,這樣就能夠很好地滿足日常業務的需要。
許多設計模式的功能類似,界限不是特別清楚(為了能讓大家更好的理解,每個章節后面都列出了類似功能設計模式之間的對比)。大家不要疑惑,設計模式不是為了特定場景而生的,而是為了讓大家可以更好和更快地開發。
設計模式只是實現了七大設計原則的具體方式,套用太多設計模式只會陷入模式套路陷阱,最后代碼寫的凌亂不堪。
在實際工作中很少會規定必須使用哪種設計模式,這樣只會限制別人。不能為了使用設計模式而去做架構,而是有了做架構的需求后,發現它符合某一類設計模式的結構,在將兩者結合。
設計模式要活學活用,不要生搬硬套。想要游刃有余地使用設計模式,需要打下牢固的程序設計語言基礎、夯實自己的編程思想、積累大量的時間經驗、提高開發能力。目的都是讓程序低耦合,高復用,高內聚,易擴展,易維護。
不僅僅是功能性需求,需求驅動還包括性能和運行時的需求,如軟件的可維護性和可復用性等方面。設計模式是針對軟件設計的,而軟件設計是針對需求的,一定不要為了使用設計模式而使用設計模式,否則可能會使設計變得復雜,使軟件難以調試和維護。
對現有的應用實例進行分析是一個很好的學習途徑,應當注意學習已有的項目,而不僅是學習設計模式如何實現,更重要的是注意在什么場合使用設計模式。
設計模式大部分都是針對面向對象的軟件設計,因此在理論上適合任何面向對象的語言,但隨著技術的發展和編程環境的改善,設計模式的實現方式會有很大的差別。在一些平臺下,某些設計模式是自然實現的。
不僅指編程語言,平臺還包括平臺引入的技術。例如,Java EE 引入了反射機制和依賴注入,這些技術的使用使設計模式的實現方式產生了改變。
4. 在編程中領悟模式
軟件開發是一項實踐工作,最直接的方法就是編程。沒有從來不下棋卻熟悉定式的圍棋高手,也沒有不會編程就能成為架構設計師的先例。掌握設計模式是水到渠成的事情,除了理論只是和實踐積累,可能會“漸悟”或者“頓悟”。
5.避免設計過度
設計模式解決的是設計不足的問題,但同時也要避免設計過度。一定要牢記簡潔原則,要知道設計模式是為了使設計簡單,而不是更復雜。如果引入設計模式使得設計變得復雜,只能說我們把簡單問題復雜化了,問題本身不需要設計模式。
這里需要把握的是需求變化的程度,一定要區分需求的穩定部分和可變部分。一個軟件必然有穩定部分,這個部分就是核心業務邏輯。如果核心業務邏輯發生變化,軟件就沒有存在的必要,核心業務邏輯是我們需要固化的。對于可變的部分,需要判斷可能發生變化的程度來確定設計策略和設計風險。要知道,設計過度與設計不足同樣對項目有害。
學習設計模式,死記硬背是沒用的,還要從實踐中理解,本教程后面會結合實例和源碼來講解如何使用設計模式。
需要特別聲明的是,在日常應用中,設計模式從來都不是單個設計模式獨立使用的。在實際應用中,通常多個設計模式混合使用,你中有我,我中有你。下圖完整地描述了設計模式之間的混用關系,希望對大家有所幫助。
創建型模式
創建型模式的主要關注點是“怎樣創建對象?”,它的主要特點是“將對象的創建與使用分離”。這樣可以降低系統的耦合度,使用者不需要關注對象的創建細節,對象的創建由相關的工廠來完成。就像我們去商場購買商品時,不需要知道商品是怎么生產出來一樣,因為它們由專門的廠商生產。
創建型模式分為以下幾種。
- 單例(Singleton)模式:某個類只能生成一個實例,該類提供了一個全局訪問點供外部獲取該實例,其拓展是有限多例模式。
- 原型(Prototype)模式:將一個對象作為原型,通過對其進行復制而克隆出多個和原型類似的新實例。
- 工廠方法(FactoryMethod)模式:定義一個用于創建產品的接口,由子類決定生產什么產品。
- 抽象工廠(AbstractFactory)模式:提供一個創建產品族的接口,其每個子類可以生產一系列相關的產品。
- 建造者(Builder)模式:將一個復雜對象分解成多個相對簡單的部分,然后根據不同需要分別創建它們,最后構建成該復雜對象。
4.1 單例模式
定義特點
指一個類只有一個實例,且該類能自行創建這個實例的一種模式。
單例模式有 3 個特點:
- 單例類只有一個實例對象;
- 該單例對象必須由單例類自行創建;
- 單例類對外提供一個訪問該單例的全局訪問點。
優缺點
單例模式的優點:
- 單例模式可以保證內存里只有一個實例,減少了內存的開銷。
- 可以避免對資源的多重占用。
- 單例模式設置全局訪問點,可以優化和共享資源的訪問。
單例模式的缺點:
- 單例模式一般沒有接口,擴展困難。如果要擴展,則除了修改原來的代碼,沒有第二種途徑,違背開閉原則。
- 在并發測試中,單例模式不利于代碼調試。在調試過程中,如果單例中的代碼沒有執行完,也不能模擬生成一個新的對象。
- 單例模式的功能代碼通常寫在一個類中,如果功能設計不合理,則很容易違背單一職責原則。
使用場景
例如,Windows 中只能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成內存資源的浪費,或出現各個窗口顯示內容的不一致等錯誤。
在計算機系統中,還有 Windows 的回收站、操作系統中的文件系統、多線程中的線程池、顯卡的驅動程序對象、打印機的后臺處理服務、應用程序的日志對象、數據庫的連接池、網站的計數器、Web 應用的配置對象、應用程序中的對話框、系統中的緩存等常常被設計成單例。
單例模式在現實生活中的應用也非常廣泛,例如公司 CEO、部門經理等都屬于單例模型。J2EE 標準中的 ServletContext 和 ServletContextConfig、Spring 框架應用中的 ApplicationContext、數據庫中的連接池等也都是單例模式。
對于 Java 來說,單例模式可以保證在一個 JVM 中只存在單一實例。單例模式的應用場景主要有以下幾個方面。
- 需要頻繁創建的一些類,使用單例可以降低系統的內存壓力,減少 GC。
- 某類只要求生成一個對象的時候,如一個班中的班長、每個人的身份證號等。
- 某些類創建實例時占用資源較多,或實例化耗時較長,且經常使用。
- 某類需要頻繁實例化,而創建的對象又頻繁被銷毀的時候,如多線程的線程池、網絡連接池等。
- 頻繁訪問數據庫或文件的對象。
- 對于一些控制硬件級別的操作,或者從系統上來講應當是單一控制邏輯的操作,如果有多個實例,則系統會完全亂套。
- 當對象需要被共享的場合。由于單例模式只允許創建一個對象,共享該對象可以節省內存,并加快對象訪問速度。如 Web 中的配置對象、數據庫的連接池等。
實現
單例模式是設計模式中最簡單的模式之一。通常,普通類的構造函數是公有的,外部類可以通過“new 構造函數()”來生成多個實例。但是,如果將類的構造函數設為私有的,外部類就無法調用該構造函數,也就無法生成多個實例。這時該類自身必須定義一個靜態私有實例,并向外提供一個靜態的公有函數用于創建或獲取該靜態私有實例。
第 1 種:懶漢式單例(雙重檢查保證效率)
該模式的特點是類加載時沒有生成單例,只有當第一次調用 getlnstance 方法時才去創建這個單例。
class Singleton {private Singleton() {}private static volatile Singleton instance;public static Singleton getInstance() {if (instance==null){synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;} }如果編寫的是多線程程序,則不要刪除上例代碼中的關鍵字 volatile 和 synchronized,否則將存在線程非安全的問題。
第 2 種:餓漢式單例
該模式的特點是類一旦加載就創建一個單例,保證在調用 getInstance 方法之前單例已經存在了。
public class HungrySingleton {private static final HungrySingleton instance = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return instance;} }餓漢式單例在類創建的同時就已經創建好一個靜態的對象供系統使用,以后不再改變,所以是線程安全的,可以直接用于多線程而不會出現問題。
單例模式的擴展
單例模式可擴展為有限的多例(Multitcm)模式,這種模式可生成有限個實例并保存在 ArrayList 中,客戶需要時可隨機獲取。
4.2 原型模式
定義特點
原型(Prototype)模式的定義如下:用一個已經創建的實例作為原型,通過復制該原型對象來創建一個和原型相同或相似的新對象。在這里,原型實例指定了要創建的對象的種類。用這種方式創建對象非常高效,根本無須知道對象創建的細節。
優缺點
原型模式的優點:
- Java 自帶的原型模式基于內存二進制流的復制,在性能上比直接 new 一個對象更加優良。
- 可以使用深克隆方式保存對象的狀態,使用原型模式將對象復制一份,并將其狀態保存起來,簡化了創建對象的過程,以便在需要的時候使用(例如恢復到歷史某一狀態),可輔助實現撤銷操作。
原型模式的缺點:
- 需要為每一個類都配置一個 clone 方法
- clone 方法位于類的內部,當對已有類進行改造的時候,需要修改代碼,違背了開閉原則。
- 當實現深克隆時,需要編寫較為復雜的代碼,而且當對象之間存在多重嵌套引用時,為了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來會比較麻煩。因此,深克隆、淺克隆需要運用得當。
使用場景
例如,Windows 操作系統的安裝通常較耗時,如果復制就快了很多。在生活中復制的例子非常多,這里不一一列舉了。
原型模式通常適用于以下場景。
- 對象之間相同或相似,即只是個別的幾個屬性不同的時候。
- 創建對象成本較大,例如初始化時間長,占用CPU太多,或者占用網絡資源太多等,需要優化資源。
- 創建一個對象需要繁瑣的數據準備或訪問權限等,需要提高性能或者提高安全性。
- 系統中大量使用該類對象,且各個調用者都需要給它的屬性重新賦值。
在 Spring 中,原型模式應用的非常廣泛,例如 scope=‘prototype’、JSON.parseObject() 等都是原型模式的具體應用。
實現
原型模式的克隆分為淺克隆和深克隆。
- 淺克隆:創建一個新對象,新對象的屬性和原來對象完全相同,對于非基本類型屬性,仍指向原有屬性所指向的對象的內存地址。
- 深克隆:創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。
注:String類型通過常量賦值時相當于基本數據類型,通過new關鍵字創建對象時便是引用數據類型。
String str1 = new String("ABC"); String str2 = new String("ABC"); System.out.println(str1 == str2); //falseString str3 = "ABC"; String str4 = "ABC"; String str5 = "AB" + "C"; System.out.println(str3 == str4); //true System.out.println(str3 == str5); // trueString a = "ABC"; String b = "AB"; String c = b + "C"; System.out.println( a == c );//false注意最后一個a與c相等的判斷:a、b在編譯時就已經被確定了,而c是引用變量,不會在編譯時就被確定。運行時b與“C”的拼接是通過StringBuilder(JDK1.5之前是StringBuffer)實現的,最后調用的StringBuilder的toString函數返回一個新的String對象。
淺克隆
Java 中的 Object 類提供了淺克隆的 clone() 方法,具體原型類只要實現 Cloneable 接口就可實現對象的淺克隆,這里的 Cloneable 接口就是抽象原型類。其代碼如下:
//具體原型類 class Realizetype implements Cloneable {public String name="coder";Realizetype() {System.out.println("具體原型創建成功!");}public Object clone() throws CloneNotSupportedException {System.out.println("具體原型復制成功!");return super.clone();} } //原型模式的測試類 public class PrototypeTest {public static void main(String[] args) throws CloneNotSupportedException {Realizetype obj1 = new Realizetype();Realizetype obj2 = (Realizetype) obj1.clone();System.out.println("obj1==obj2?" + (obj1 == obj2));System.out.println("clone1.name==realizetype1.name?"+(clone1.name==realizetype1.name));} }運行結果:
具體原型創建成功! 具體原型復制成功! obj1==obj2?false深克隆
兩種實現方式:重寫clone、通過對象序列化
方法一:重寫clone
與通過重寫clone方法實現淺拷貝的基本思路一樣,只需要為對象圖的每一層的每一個對象都實現Cloneable接口并重寫clone方法,最后在最頂層的類的重寫的clone方法中調用所有的clone方法即可實現深拷貝。簡單的說就是:每一層的每個對象都進行淺拷貝=深拷貝。
public class DeepCopy {public static void main(String[] args) {Student s1 = new Student("s1", new Age(20), 175);Student clone = (Student) s1.clone();System.out.println(clone.getAge()==s1.getAge());System.out.println();} }class Age implements Cloneable {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Age(int age) {this.age = age;}@Overridepublic String toString() {return "Age{" +"age=" + age +'}';}@Overrideprotected Object clone() {Object object = null;try {object = super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return object;} }class Student implements Cloneable {private String name;private Age age;private int length;public String getName() {return name;}public void setName(String name) {this.name = name;}public Age getAge() {return age;}public void setAge(Age age) {this.age = age;}public int getLength() {return length;}public void setLength(int length) {this.length = length;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", length=" + length +'}';}public Student(String name, Age age, int length) {this.name = name;this.age = age;this.length = length;}@Overrideprotected Object clone() {Object object = null;try {object = super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}Student student = (Student) object;student.age = (Age) student.getAge().clone();return object;} }方法二:通過對象序列化
雖然層次調用clone方法可以實現深拷貝,但是顯然代碼量實在太大。特別對于屬性數量比較多、層次比較深的類而言,每個類都要重寫clone方法太過繁瑣。
將對象序列化為字節序列后,默認會將該對象的整個對象圖進行序列化,再通過反序列即可完美地實現深拷貝。
import java.io.*;public class DeepCopy {public static void main(String[] args) throws IOException, ClassNotFoundException {Age age = new Age(20);Student s1 = new Student("s1", age, 180);ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(s1);oos.flush();ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);Student clone = (Student) ois.readObject();ois.close();bis.close();oos.close();bos.close();System.out.println(clone == s1);System.out.println();} }class Age implements Serializable {private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Age(int age) {this.age = age;}@Overridepublic String toString() {return "Age{" +"age=" + age +'}';}}class Student implements Serializable {private String name;private Age age;private int length;public String getName() {return name;}public void setName(String name) {this.name = name;}public Age getAge() {return age;}public void setAge(Age age) {this.age = age;}public int getLength() {return length;}public void setLength(int length) {this.length = length;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", length=" + length +'}';}public Student(String name, Age age, int length) {this.name = name;this.age = age;this.length = length;} }原型模式的擴展
原型模式可擴展為帶原型管理器的原型模式,它在原型模式的基礎上增加了一個原型管理器 PrototypeManager 類。該類用 HashMap 保存多個復制的原型,Client 類可以通過管理器的 get(String id) 方法從中獲取復制的原型。
工廠模式
在日常開發中,凡是需要生成復雜對象的地方,都可以嘗試考慮使用工廠模式來代替。
注意:上述復雜對象指的是類的構造函數參數過多等對類的構造有影響的情況,因為類的構造過于復雜,如果直接在其他業務類內使用,則兩者的耦合過重,后續業務更改,就需要在任何引用該類的源代碼內進行更改,光是查找所有依賴就很消耗時間了,更別說要一個一個修改了。
工廠模式的定義:定義一個創建產品對象的工廠接口,將產品對象的實際創建工作推遲到具體子工廠類當中。這滿足創建型模式中所要求的“創建與使用相分離”的特點。
按實際業務場景劃分,工廠模式有 3 種不同的實現方式,分別是簡單工廠模式、工廠方法模式和抽象工廠模式。
我們把被創建的對象稱為“產品”,把創建產品的對象稱為“工廠”。如果要創建的產品不多,只要一個工廠類就可以完成,這種模式叫“簡單工廠模式”。
在簡單工廠模式中創建實例的方法通常為靜態(static)方法,因此簡單工廠模式(Simple Factory Pattern)又叫作靜態工廠方法模式(Static Factory Method Pattern)。
簡單來說,簡單工廠模式有一個具體的工廠類,可以生成多個不同的產品,屬于創建型設計模式。
簡單工廠模式每增加一個產品就要增加一個具體產品類和一個對應的具體工廠類,這增加了系統的復雜度,違背了“開閉原則”。
“工廠方法模式”是對簡單工廠模式的進一步抽象化,其好處是可以使系統在不修改原來代碼的情況下引進新的產品,即滿足開閉原則。
4.3 簡單工廠模式
定義特點
我們把被創建的對象稱為“產品”,把創建產品的對象稱為“工廠”。如果要創建的產品不多,只要一個工廠類就可以完成,這種模式叫“簡單工廠模式”。
在簡單工廠模式中創建實例的方法通常為靜態(static)方法,因此簡單工廠模式(Simple Factory Pattern)又叫作靜態工廠方法模式(Static Factory Method Pattern)。
優缺點
優點:
1- 工廠類包含必要的邏輯判斷,可以決定在什么時候創建哪一個產品的實例。客戶端可以免除直接創建產品對象的職責,很方便的創建出相應的產品。工廠和產品的職責區分明確。
- 客戶端無需知道所創建具體產品的類名,只需知道參數即可。
- 也可以引入配置文件,在不修改客戶端代碼的情況下更換和添加新的具體產品類。
缺點:
- 簡單工廠模式的工廠類單一,負責所有產品的創建,職責過重,一旦異常,整個系統將受影響。且工廠類代碼會非常臃腫,違背高聚合原則。
- 使用簡單工廠模式會增加系統中類的個數(引入新的工廠類),增加系統的復雜度和理解難度
- 系統擴展困難,一旦增加新產品不得不修改工廠邏輯,在產品類型較多時,可能造成邏輯過于復雜
- 簡單工廠模式使用了 static 工廠方法,造成工廠角色無法形成基于繼承的等級結構。
使用場景
對于產品種類相對較少的情況,考慮使用簡單工廠模式。使用簡單工廠模式的客戶端只需要傳入工廠類的參數,不需要關心如何創建對象的邏輯,可以很方便地創建所需產品。
實現
簡單工廠模式的主要角色如下:
- 簡單工廠(SimpleFactory):是簡單工廠模式的核心,負責實現創建所有實例的內部邏輯。工廠類的創建產品類的方法可以被外界直接調用,創建所需的產品對象。
- 抽象產品(Product):是簡單工廠創建的所有對象的父類,負責描述所有實例共有的公共接口。
- 具體產品(ConcreteProduct):是簡單工廠模式的創建目標。
其結構圖如下圖所示。
4.4 工廠方法模式
定義特點
在《簡單工廠模式》一節我們介紹了簡單工廠模式,提到了簡單工廠模式違背了開閉原則,而“工廠方法模式”是對簡單工廠模式的進一步抽象化,其好處是可以使系統在不修改原來代碼的情況下引進新的產品,即滿足開閉原則。
優缺點
優點:
- 用戶只需要知道具體工廠的名稱就可得到所要的產品,無須知道產品的具體創建過程。
- 靈活性增強,對于新產品的創建,只需多寫一個相應的工廠類。
- 典型的解耦框架。高層模塊只需要知道產品的抽象類,無須關心其他實現類,滿足迪米特法則、依賴倒置原則和里氏替換原則。
缺點:
- 類的個數容易過多,增加復雜度
- 增加了系統的抽象性和理解難度
- 抽象產品只能生產一種產品,此弊端可使用抽象工廠模式解決。
使用場景
- 客戶只知道創建產品的工廠名,而不知道具體的產品名。如 TCL 電視工廠、海信電視工廠等。
- 創建對象的任務由多個具體子工廠中的某一個完成,而抽象工廠只提供創建產品的接口。
- 客戶不關心創建產品的細節,只關心產品的品牌
實現
工廠方法模式由抽象工廠、具體工廠、抽象產品和具體產品等4個要素構成。
工廠方法模式的主要角色如下。
其結構圖如圖 1 所示。
4.5 抽象工廠模式
同種類稱為同等級,也就是說:工廠方法模式只考慮生產同等級的產品,但是在現實生活中許多工廠是綜合型的工廠,能生產多等級(種類) 的產品,如農場里既養動物又種植物,電器廠既生產電視機又生產洗衣機或空調,大學既有軟件專業又有生物專業等。
本節要介紹的抽象工廠模式將考慮多等級產品的生產,將同一個具體工廠所生產的位于不同等級的一組產品稱為一個產品族,下圖所示的是海爾工廠和 TCL 工廠所生產的電視機與空調對應的關系圖。
定義特點
抽象工廠(AbstractFactory)模式的定義:是一種為訪問類提供一個創建一組相關或相互依賴對象的接口,且訪問類無須指定所要產品的具體類就能得到同族的不同等級的產品的模式結構。
抽象工廠模式是工廠方法模式的升級版本,工廠方法模式只生產一個等級的產品,而抽象工廠模式可生產多個等級的產品。
優缺點
抽象工廠模式除了具有工廠方法模式的優點外,其他主要優點如下。
- 可以在類的內部對產品族中相關聯的多等級產品共同管理,而不必專門引入多個新的類來進行管理。
- 當需要產品族時,抽象工廠可以保證客戶端始終只使用同一個產品的產品組。
- 抽象工廠增強了程序的可擴展性,當增加一個新的產品族時,不需要修改原代碼,滿足開閉原則。
其缺點是:當產品族中需要增加一個新的產品時,所有的工廠類都需要進行修改。增加了系統的抽象性和理解難度。
使用場景
使用抽象工廠模式一般要滿足以下條件:
- 系統中有多個產品族,每個具體工廠創建同一族但屬于不同等級結構的產品。
- 系統一次只可能消費其中某一族產品,即同族的產品一起使用。
抽象工廠模式最早的應用是用于創建屬于不同操作系統的視窗構件。如 Java 的 AWT 中的 Button 和 Text 等構件在 Windows 和 UNIX 中的本地實現是不同的。
抽象工廠模式通常適用于以下場景:
- 當需要創建的對象是一系列相互關聯或相互依賴的產品族時,如電器工廠中的電視機、洗衣機、空調等。
- 系統中有多個產品族,但每次只使用其中的某一族產品。如有人只喜歡穿某一個品牌的衣服和鞋。
- 系統中提供了產品的類庫,且所有產品的接口相同,客戶端不依賴產品實例的創建細節和內部結構。
實現
抽象工廠模式的主要角色如下。
抽象工廠結構如下:
擴展
抽象工廠模式的擴展有一定的“開閉原則”傾斜性:
- 當增加一個新的產品族時只需增加一個新的具體工廠,不需要修改原代碼,滿足開閉原則。
- 當產品族中需要增加一個新種類的產品時,則所有的工廠類都需要進行修改,不滿足開閉原則。
另一方面,當系統中只存在一個等級結構的產品時,抽象工廠模式將退化到工廠方法模式。
4.6 建造者模式
定義特點
建造者(Builder)模式的定義:指將一個復雜對象的構造與它的表示分離,使同樣的構建過程可以創建不同的表示,這樣的設計模式被稱為建造者模式。它是將一個復雜的對象分解為多個簡單的對象,然后一步一步構建而成。它將變與不變相分離,即產品的組成部分是不變的,但每一部分是可以靈活選擇的。
通過前面的學習,我們已經了解了建造者模式,那么它和工廠模式有什么區別呢?
- 建造者模式更加注重方法的調用順序,工廠模式注重創建對象。
- 創建對象的力度不同,建造者模式創建復雜的對象,由各種復雜的部件組成,工廠模式創建出來的對象都一樣
- 關注重點不一樣,工廠模式只需要把對象創建出來就可以了,而建造者模式不僅要創建出對象,還要知道對象由哪些部件組成。
- 建造者模式根據建造過程中的順序不一樣,最終對象部件組成也不一樣。
優缺點
該模式的主要優點如下:
- 封裝性好,構建和表示分離。
- 擴展性好,各個具體的建造者相互獨立,有利于系統的解耦。
- 客戶端不必知道產品內部組成的細節,建造者可以對創建過程逐步細化,而不對其它模塊產生任何影響,便于控制細節風險。
其缺點如下:
- 產品的組成部分必須相同,這限制了其使用范圍。
- 如果產品的內部變化復雜,如果產品內部發生變化,則建造者也要同步修改,后期維護成本較大。
建造者(Builder)模式和工廠模式的關注點不同:建造者模式注重零部件的組裝過程,而工廠方法模式更注重零部件的創建過程,但兩者可以結合使用。
使用場景
建造者模式唯一區別于工廠模式的是針對復雜對象的創建。也就是說,如果創建簡單對象,通常都是使用工廠模式進行創建,而如果創建復雜對象,就可以考慮使用建造者模式。
當需要創建的產品具備復雜創建過程時,可以抽取出共性創建過程,然后交由具體實現類自定義創建流程,使得同樣的創建行為可以生產出不同的產品,分離了創建與表示,使創建產品的靈活性大大增加。
建造者模式主要適用于以下應用場景:
實現
建造者(Builder)模式的主要角色如下:
總結
以上是生活随笔為你收集整理的java设计模式(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven 详情查考 mav
- 下一篇: Egret Engine(二十六):Mo