23種設計模式(概念、原則、場景、優點、缺點、應用)簡述
《大話設計模式》中提到了 24種設計模式:
簡單工廠模式,策略模式、裝飾模式、代理模式、工廠方法模式、原型模式、模板方法模式、外觀模式、建造者模式、觀察者模式、抽象工廠模式、狀態模式、介面卡模式、備忘錄模式、組合模式、迭代器模式、單例模式、橋接模式、命令模式、職責鏈模式、中介者模式、享元模式、直譯器模式、訪問者模式。
按照型別,可分為3類:
1、 建立型模式:抽象工廠、建造者模式、工廠方法、原型模式、單例模式;
建立型模式抽象了例項化的過程。建立性模式隱藏了這些類的例項是如何被建立和放在一起,整個系統關於這些物件所知道的是由抽象類所定義的介面。這樣,建立性模式在建立了什麼、誰建立它、她是怎麼被建立的、以及何時建立方面提供了靈活性。建立相應數目的原型並克隆她們通常比每次用適合的狀態手工例項化該類更方便。
2、 結構型模式:介面卡模式、橋接模式、組合模式、裝飾者模式、外觀模式、享元模式、代理模式;
3、 行為型模式:觀察者模式、模板方法、命令模式、狀態模式、職責鏈模式、直譯器模式、中介者模式、訪問者模式、策略模式、備忘錄模式、迭代器模式。
4、 MVC模式:集觀察者、組合、策略為一體,是多種模式的綜合應用,算是一種架構模式。
下面按照【概念】+【原則】+【場景】+【優點】+【缺點】+【應用】分別簡述一下24種設計模式:
抽象工廠模式(Abstract Factory) 提供一個建立一系列相關或互相依賴物件的介面,而無需指定它們具體的類。
原則:LSP 里氏替換原則
場景:建立不同的產品物件,客戶端應使用不同的具體工廠。
優點:
a) 改變具體工廠即可使用不同的產品配置,使改變一個應用的具體工廠變得很容易。
b) 讓具體的建立例項過程與客戶端分離,客戶端通過抽象介面操作例項,產品的具體類名也被具體工廠的實現分離。
缺點:如果要新增方法,改動極大。
應用:
a)jdk中連線資料庫的程式碼是典型的抽象工廠模式,每一種資料庫只需提供一個統一的介面:Driver(工廠類),並實現其中的方法即可。不管是jdbc還是odbc都能夠通過擴充套件產品線來達到連線自身資料庫的方法。
b)java.util.Collection 介面中定義了一個抽象的 iterator() 方法,該方法就是一個工廠方法。對於 iterator() 方法來說 Collection 就是一個抽象工廠
建造者模式(Builder) 【又名,生成器模式】:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
原則:依賴倒轉原則
場景:如果需要將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。建造者模式是當建立複雜物件的演算法應該獨立於該物件的組成部分以及它們的裝配方式時適用的模式。
優點:使得建造程式碼與表示程式碼分離。
缺點:1、增加程式碼量;2、Builder只是一個替代構造器的選擇,不能直接用於降低非建構函式方法的引數數量。
應用:StringBuilder和StringBuffer的append()方法
工廠方法模式(Factory Method) 定義一個用於建立物件的介面,讓子類決定例項化哪一個類,工廠方法使一個類的例項化延遲到其子類。
原則:開放封閉原則
場景:不改變工廠和產品體系,只是要擴充套件產品(變化)。
優點:是簡單工廠模式的進一步抽象和推廣,既保持了簡單工廠模式的優點(工廠類中包含了必要的邏輯判斷,根據客戶端的選擇條件動態例項化相關的類。對於客戶端來說,去除了與具體產品的依賴),而且克服了簡單工廠的缺點(違背了開放封閉原則)。
缺點:每增加一個產品,就需要增加一個產品工廠的類,增加了額外的開發。(用反射可以解決)。
應用:
1. Collection中的iterator方法;
2. java.lang.Proxy#newProxyInstance()
3. java.lang.Object#toString()
4. java.lang.Class#newInstance()
5. java.lang.reflect.Array#newInstance()
6. java.lang.reflect.Constructor#newInstance()
7. java.lang.Boolean#valueOf(String)
8. java.lang.Class#forName()
原型模式(prototype) 【又名,生成器模式】:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。
原則:
場景:在初始化資訊不發生變化的情況,用克隆進行拷貝。
優點:隱藏了物件建立的細節,大大提升了效能。不用重新初始化物件,而是動態的獲得物件執行時的狀態。
缺點:深複製 or 淺複製 。
應用:JDK中的Date類。
單例模式(Singleton) 保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
原則:封裝
場景:通常,我們可以讓一個全域性變數使得一個物件被訪問,但它不能防止你例項化多個物件,一個最好的辦法就是,讓類自身負責儲存它的唯一例項。這個類可以保證沒有其他例項可以被建立,而且它可以提供一個訪問該例項的方法。
優點:對唯一例項的受控訪問。
缺點:餓漢式/懶漢式 多執行緒同時訪問時可能造成多個例項。
應用:java.lang.Runtime; GUI中也有一些(java.awt.Toolkit#getDefaultToolkit() java.awt.Desktop#getDesktop())
介面卡模式(Adapter) 將一個類的介面轉換成客戶希望的另外一個介面。Adapter模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。
在GoF的設計模式中,介面卡有兩種型別,類介面卡模式和物件介面卡模式。
a) 類介面卡模式:通過多重繼承對一個介面與另一個介面進行匹配,而C#,Java等語言都不支援多重繼承,也就是一個類只有一個父類。
b) Java一般都指的是 物件介面卡模式
場景:介面卡是為了複用一些現有的類。系統的資料和行為都正確,但是介面不符,這時採用介面卡模式,使原有物件和新介面匹配。
優點:能夠複用現存的類,客戶端統一呼叫同一介面,更簡單、直接、緊湊。
缺點:介面卡模式有點兒“亡羊補牢”的感覺,設計階段要避免使用。
應用:在Java jdk中,介面卡模式使用場景很多,如集合包中Java.util.Arrays#asList()、IO包中java.io.InputStreamReader(InputStream)、java.io.OutputStreamWriter(OutputStream) 等
橋接模式(Bridge) 將抽象部分與它的實現部分分離,使它們都可以獨立的變化。
原則:合成/聚合複用原則
場景:實現系統可能有多角度分類,每一種分類都有可能變化,那麼就把這種多角度分離出來讓它們獨立變化,減少它們之間的耦合。
優點:減少各部分的耦合。 分離抽象和實現部分,更好的擴充套件性,可動態地切換實現、可減少子類的個數。
缺點:1、橋接模式的引入會增加系統的理解與設計難度,由於聚合關聯關係建立在抽象層,要求開發者針對抽象進行設計與程式設計。 2、橋接模式要求正確識別出系統中兩個獨立變化的維度,因此其使用範圍具有一定的侷限性
應用:Collections類中的sort()方法;AWT;JDBC資料庫訪問介面API;
組合模式(Composite) 將物件組合成樹形結構以表示“部分-整體”的層次結構。
場景:需求中體現部分與整體層次結構時,以及希望使用者可以忽略組合物件與單個物件的不同,統一使用組合結構中的所有物件時,就應該考慮使用組合模式了。
優點:組合模式讓客戶可以一致的使用組合結構和單個物件。
缺點:使設計變得更加抽象,物件的業務規則如果很複雜,則實現組合模式具有很大挑戰性,而且不是所有的方法都與葉子物件子類都有關聯。
應用:JDK中AWT包和Swing包的設計是基於組合模式,在這些介面包中為使用者提供了大量的容器構件(如Container)和成員構件(如Checkbox、Button和TextComponent等),他們都是繼承、關聯自抽象元件類Component。
裝飾模式(Decorator) 動態地給一個物件新增一些額外的職責,就增加功能來說,裝飾模式比生成子類更靈活。
場景:裝飾模式是為了已有功能動態地新增更多功能的一種方式,當系統需要新功能的時候,是向舊類中新增新的程式碼,這些新的程式碼通常裝飾了原有類的核心職責或主要行為。裝飾著模式把每個要裝飾的功能放在單獨的類中,並讓這個類包裝它所要裝飾的物件,當需要執行特殊行為時,客戶程式碼就可以在執行時根據需要有選擇的、按順序地使用裝飾功能包裝物件。
優點:把類中的裝飾功能從類中搬移出去,簡化原有的類。有效的把類的核心職責和裝飾功能區分開,去除相關類中重複的裝飾邏輯。
缺點:利用裝飾器模式,常常造成設計中有大量的小類,數量實在太多,可能會造成使用此API程式設計師的困擾。
應用:Java I/O使用裝飾模式設計,JDK中還有很多類是使用裝飾模式設計的,如:Reader類、Writer類、OutputStream類等。
外觀模式(facade) 為子系統中的一組介面提供一個一致的介面,此模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
原則:完美的體現了依賴倒轉原則和迪米特法則。
場景:
a) 設計階段:需有意識的將不同的兩個層分離。
b) 開發階段:增加外觀façade提供一個簡單的介面,應對子類的重演和演化。
c) 維護期間:使用façade類,為遺留程式碼提供清晰簡單的介面,讓新系統與façade互動,façade與遺留程式碼互動所有複雜的工作。
優點:1、客戶對子系統的使用變得簡單了,減少了與子系統的關聯物件,實現了子系統與客戶之間的鬆耦合關係。 2、只是提供了一個訪問子系統的統一入口,並不影響使用者直接使用子系統類 3、降低了大型軟體系統中的編譯依賴性,並簡化了系統在不同平臺之間的移植過程。
缺點:1、不能很好地限制客戶使用子系統類,如果對客戶訪問子系統類做太多的限制則減少了可變性和靈活性 2、在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的原始碼,違背了“開閉原則”。
享元模式(Flyweight) 運用共享技術有效的支援大量細粒度的物件。
場景:如果一個應用程式使用了大量的物件,而大量的這些物件造成了很大儲存開銷時就應該考慮使用享元模式;還有就是物件大多數狀態都可為外部狀態,如果刪除物件的外部狀態,那麼可以用相對較少的共享物件取代很多組物件,此時可以考慮使用享元模式。
優點:享元模式可以避免大量非常相似類的開銷。程式中,大量細粒度的類例項來表示資料,如果它們除了幾個引數外基本相同,那麼把它們轉移到類例項的外面,在方法呼叫時將它們傳遞進來,就可以通過共享大幅度減少單個例項的數目。
缺點:1、由於享元模式需要區分外部狀態和內部狀態,使得應用程式在某種程度上來說更加複雜化了。2、為了使物件可以共享,享元模式需要將享元物件的狀態外部化,而讀取外部狀態使得執行時間變長。
應用:String 類。
代理模式(proxy) 為其他物件提供一種代理以控制對這個物件的訪問。
原則:代理模式就是在訪問物件時引入一定程度的間接性。(迪米特法則?)
場景:
a) 遠端代理:為一個物件在不同的地址空間提供區域性代表,這樣可以隱藏一個物件存在於不同地址空間的事實。【WebService,客戶端可以呼叫代理解決遠端訪問問題】
b) 虛擬代理:根據需要建立開銷很大的物件,通過它來存放例項化需要很長時間地真實物件。【比如Html網頁的圖片,代理儲存的是真實圖片的路徑和尺寸】
c) 安全代理:用來控制真實物件的訪問許可權。
d) 智慧指引:當呼叫真實的物件時,代理處理另一些事。【如計算機真實物件的引用次數,代理在訪問一個物件的時候回附加一些內務處理,檢查物件是否被鎖定、是否該釋放、是否該裝入記憶體等等】
優點:1)代理模式能將代理物件與真正被呼叫的物件分離,在一定程度上降低了系統的耦合度。2)代理模式在客戶端和目標物件之間起到一箇中介作用,這樣可以起到保護目標物件的作用。代理物件也可以對目標物件呼叫之前進行其他操作。
缺點:1)在客戶端和目標物件增加一個代理物件,會造成請求處理速度變慢。2)增加了系統的複雜度。
應用:java.lang.reflect 包中的Proxy類和InvocationHandler 介面提供了生成動態代理類的能力。
觀察者模式(Publish/Subscribe) 【又名 釋出-訂閱模式】:定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態發生變化時,會通知所有觀察者物件,讓它們能夠自動更新自己。
場景:將一個系統分割成一系列互相協作的類,有一個缺點:需要維護相關物件間的一致性。緊密的耦合會給維護和擴充套件帶來不便。觀察者模式就是為了解耦而誕生的,讓原本一個物件依賴另一個物件的關係,變成了兩方都依賴於抽象,而不再依賴於具體,從而使得各自的變化都不會影響另一邊的變化。
優點:解耦。
缺點:如果在被觀察者之間有迴圈依賴的話,被觀察者會觸發它們之間進行迴圈呼叫,導致系統崩潰。在使用觀察者模式是要特別注意這一點。
應用:java.util.Observer , java類庫實現觀察著(Observer)模式的類和介面。
模板方法模式(Template Method) 定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。
原則:程式碼複用平臺。
場景:遇到由一系列步驟構成的過程需要執行,這個過程從高層次上看是相同的,但是有些步驟的實現可能不同,這個時候就需要考慮用模板方法模式了。
優點:模板方法模式是通過把不變行為搬移到超類,去除子類中重複程式碼來實現它的優勢,提供了一個程式碼複用平臺,幫助子類擺脫重複的不變行為的糾纏。
缺點:如果父類中可變的基本方法太多,將會導致類的個數增加,系統更加龐大。
應用:AbstractClass抽象類裡面的TemplateMethod()就是模板方法。
命令模式(command) 將一個請求封裝為一個物件,從而使你可用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。
原則:敏捷開發原則
場景:對請求排隊或記錄請求日誌,以及支援可撤銷的操作等行為。
優點:
a) 命令模式把請求一個操作的物件與知道怎麼執行一個操作的物件分割開。
b) 它能較容易的設計一個命令佇列。
c) 在需要的情況下,可以較容易的將命令記入日誌。
d) 允許接收請求的一方決定是否要否決請求。
e) 可以容易的實現對請求的撤銷和重做。
f) 由於加進新的具體命令類不影響其他類,因此增加新的具體命令類很容易。
缺點:會增加系統的複雜性,這裡的複雜性應該主要指的是類的數量。
應用:
1. java.util.Timer類中scheduleXXX()方法
2. java Concurrency Executor execute()方法
3. java.lang.reflect.Methodinvoke()方法
狀態模式(state) 當一個物件的內在狀態改變時,允許改變其行為,這個物件看起來像是改變了其類。
原則:單一職責原則
場景:當一個物件的行為取決於它的狀態,並且它必須在執行時刻根據狀態改變它的行為時,可以考慮使用狀態模式了。
優點:狀態模式主要解決的是當控制一個物件狀態轉換的條件表示式過於複雜的情況。把狀態的判斷邏輯轉移到表示不同狀態的一系列類當中,可以把複雜的判斷邏輯簡化。【消除龐大的條件分支語句】。
缺點:違背開放-封閉原則
應用:
1. java.util.Iterator
2. javax.faces.lifecycle.LifeCycle#execute()
職責鏈模式(chain of responsibility) 使多個物件都有機會處理請求,從而避免請求的傳送者和接受者之間的耦合關係。將這個物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。
場景:當客戶提交一個請求時,請求是沿鏈傳遞直至有一個物件負責處理它。
優點:使得接收者和傳送者都沒有對方的明確資訊,且鏈中物件自己也不知道鏈結構,結果是職責鏈可以簡化物件的相互連線,它們只需要保持一個指向其後繼者的引用,而不需要保持它所有的候選接收者的引用。開發者可以隨時的增加或者修改處理一個請求的結構,增強了給物件指派職責的靈活性。
缺點:一個請求極有可能到了鏈的末端都得不到處理,或者因為沒有正確配置而得不到處理。
直譯器模式(interpreter) 給定一個語言,定義它的文法的一種表示,並定義一個直譯器,這個直譯器使用該表示來解釋語言中的句子。
原則:依賴倒轉原則
場景:如果一種特定型別問題發生的頻率足夠高,那麼可能就值得將該問題的各個例項表述為一個簡單語句中的句子。這樣就可以構建一個直譯器,該直譯器通過解釋這些句子來解決該問題。當一個語言需要執行,並且你可將該語言中的句子表示為一個抽象語法樹時,可以用直譯器模式。
優點:直譯器很容易改變和擴充套件文法,因為該模式使用類來表示文法規則,可以使用繼承來改變或擴充套件文法,也比較容易實現文法。因為定義抽象語法樹中各個節點的類的實現大體類似,這些類都易於直接編寫。
缺點:直譯器模式為文法中的每一條規則至少定義了一個類,因此包含許多規則的文法可能難以管理和維護,建議當文法非常複雜時,使用其他技術(語法分析程式、編譯器生成器)。
應用:
1. java.util.Pattern
2. java.text.Normalizer
3. java.text.Format
4. javax.el.ELResolver
中介者模式(mediator) 用一箇中介物件來封裝一系列的物件互動。中介者使各物件不需要顯示的相互引用,從而使其耦合鬆散,而且可以獨立的改變它們之間的互動。
場景:一般應用於一組物件以定義良好但是複雜的方式進行通訊的場合,以及想定製一個分佈在多個類的行為,而又不想生成太多子類的場合。【例如,Form窗體,或者aspx頁面】。
優點:
a) 抽象中介者類(Mediator)減少了抽象同事類(colleague)之間的耦合,是的可以獨立的改變和複用各個類。
b) 由於把物件如何協作進行了抽象,將中介作為一個獨立的概念並將其封裝在一個物件中,這樣關注的物件就從物件各自本身的行為轉移到它們之間的互動上來,也就是站在一個更巨集觀的角度去看待系統。
缺點:控制集中化導致了中介者的複雜化。
應用:
1. java.util.Timer
2. java.util.concurrent.Executor#execute()
3. java.util.concurrent.ExecutorService#submit()
4. java.lang.reflect.Method#invoke()
訪問者模式 (Vistor) 生成器模式】:(GoF中最複雜的一個模式)表示一個作用於某物件結構中的各元素的操作,它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。
場景:訪問者模式適合有穩定的資料結構、又有易於變化的演算法】訪問者模式適用於資料結構相對穩定的系統,它把資料結構和作用於結構上的操作之間的耦合解脫開,是的操作集合可以相對自由的演化。訪問者模式的目的是要把處理從資料結構中分離出來。
優點:增加新的操作很容易。新的操作就是新的訪問者。
缺點:很難增加新的資料結構。
應用:
1. javax.lang.model.element.AnnotationValue和AnnotationValueVisitor
2. javax.lang.model.element.Element和ElementVisitor
3. javax.lang.model.type.TypeMirror和TypeVisitor
策略模式(strategy) 它定義了演算法家族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化不會影響到使用演算法的使用者。
場景:策略模式不僅可以用來封裝演算法,幾乎可以封裝縫合型別的規則,不同的業務邏輯都可以考慮用策略模式處理變化。
優點:策略模式的策略類為上下文定義了一系列可供重用的演算法或行為,繼承有助於析取出這些演算法中的公共功能。另外,策略模式簡化了單元測試,因為每一個演算法都有自己的類,可以通過自己的介面單獨測試。當不同的行為堆砌在一個類中,很難避免使用switch語句。但是將這些行為封裝在一個一個獨立的策略類中,可以在使用這些行為的類中消除條件語句
缺點:基本的策略模式,選擇權在客戶端,具體實現轉給策略模式的上下文物件。這並不好。使用策略模式和工廠類結合,可以減輕客戶端的職責。但是還是不夠完美,使用反射才能真正快樂。
應用:
1. java.util.Comparator#compare()
2. javax.servlet.http.HttpServlet
3. javax.servlet.Filter#doFilter()
備忘錄模式(Memento) 在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣以後就可將該物件恢復到原先儲存的狀態。
場景:Memento封裝要儲存的細節,適合功能負責但需要維護或記錄屬性歷史的類,或者是需要儲存的屬性只是眾多屬性中的一個小部分。
優點:使用備忘錄模式可以把複雜的發起人內部資訊對其他的物件遮蔽起來,從而可以恰當地保持封裝的邊界。
缺點:如果發起人角色的狀態需要完整地儲存到備忘錄物件中,那麼在資源消耗上面備忘錄物件會很昂貴。
應用:
1. java.util.Date
2. java.io.Serializable
迭代器模式(Iterator) 提供一種方法順序訪問一個聚合物件中各個元素,而又不暴露該物件的內部表示。
場景:當需要對聚集有多種方式遍歷時,可以考慮使用迭代器。
優點:迭代器模式就是分離了集合物件的遍歷行為,抽象出一個迭代器來負責,這樣既可以做到不暴露集合的內部結構,又可以讓外部程式碼透明的訪問集合內部的資料。
缺點:由於迭代器模式將儲存資料和遍歷資料的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的複雜性。
應用:collection容器使用了迭代器模式
設計模式在JDK的應用:http://blog.csdn.net/u013782203/article/details/52214393