23種設計模式的總結~以及區別、應用

簡介

設計模式目的:爲了可重用代碼,保證代碼的可靠性,更容易被他人理解。
設計模式的六大原則:
總原則:開閉原則,即對擴展開放,對修改關閉。
1 單一職責原則:每個類應該實現單一的職責,否則應該把類拆分。
2 里氏替換原則:任何基類可以出現的地方,子類一定可以出現。它是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。
3 依賴倒轉原則:這是開閉原則的基礎,對接口編程,依賴於抽象而不依賴於具體。
4 接口隔離原則:使用多個隔離的接口,比使用單個接口要好。每個接口不存在子類用不到卻必須實現的方法,否則要將接口拆分。
5 迪米特法則(最少知道原則):一個實體應當儘量少的與其他實體之間發生相互作用,使得系統的功能模塊相對獨立。
6 合成複用原則:儘量使用合成/聚合方式,而不是使用繼承。

設計模式分爲三大類:
創建型模式:(5)工廠方法模式 抽象工廠模式 單例模式 建造者模式 原型模式 (簡單工廠模式)
結構型模式:(7)代理模式 裝飾器模式 適配器模式 外觀模式 組合模式 享元模式 橋接模式
行爲型模式:(11)觀察者模式 責任鏈模式 模板方法模式 策略模式 迭代子模式 命令模式 狀態模式 備忘錄模式 訪問者模式 中介者模式 解釋器模式

其實還有兩類:併發型模式和線程池模式
設計模式之間的關係見下圖:
這裏寫圖片描述

創建型模式

  • 簡單工廠模式

簡單工廠模式並不屬於23種模式中的一種,但是還是很有必要了解一下。
簡單工廠模式:有一個工廠類,在工廠類中進行判斷,創建需要的功能類。

不必使用具體的功能類去創建該類的實例,創建實例的操作交給工廠類去完成。但是,當需要增加一個新的功能類的時候,就需要在工廠類中增加一個判斷。

  • 工廠方法模式

定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法模式使一個類的實例化延遲到子類。

當新增加一個類的時候,不需要對工廠類進行修改,但是當新增一個功能類的時候,需要創建對應的工廠類。這樣,就會創建過多的類,不如策略模式。

  • 抽象工廠模式

提供一個創建一系列相關或者相互依賴對象的接口,而無需指定它們具體的類。

抽象工廠模式是工廠方法模式的升級版本。它用來創建一組相關或者相互依賴的對象。
與工廠方法模式的區別:工廠方法模式針對的是一個產品等級結構,而抽象工廠模式針對的是多個產品等級結構。通常,一個產品結構表現爲抽象類或者接口,抽象工廠模式所提供的產品衍生自不同的接口或者抽象類。
工廠方法模式:
一個抽象產品類,可以派生出多個具體產品類。
一個抽象工廠類,可以派生出多個具體工廠類。
每個具體工廠類,只能創建一個具體產品類的實例。
抽象工廠模式:
多個抽象產品類,每個抽象產品類可以派生出多個具體產品類。
一個抽象工廠類,可以派生出多個具體工廠類。
每個具體工廠類,可以創建多個具體產品類的實例。也就是創建一個產品家族下的多個產品。

例子:工廠可以生產鼠標和鍵盤,但是微軟和羅技都有這兩個產品。那麼,微軟和羅技就可以看成是兩個產品族,分別由A工廠和B工廠生產各自的鼠標和鍵盤。A和B對應於抽象工廠,每個工廠生產的鼠標和鍵盤對應於工廠方法。
如果使用工廠方法模式,只要替換生成鍵盤的工廠方法就可以把鍵盤從羅技換到微軟。
但是使用了抽象工廠模式,只要換家工廠,就可以同時換鼠標和鍵盤。如果需要的產品(鼠標 鍵盤..)有很多,使用抽象工廠模式一次性替換很方便。

  • 單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
有懶漢和餓漢兩種模式,同時要注意線程安全的寫法。

應用:對於無狀態的類使用單例模式,節省內存資源。
Servlet中的實例就是單例模式,但是是多線程。
Spring中創建的Bean對象默認是單例模式,這樣就不用爲每個請求創建一個實例對象,減少性能開銷。但是Struts2中的Action是多例模式,針對每個請求都會創建一個實例,因此是線程安全的。

  • 建造者模式

將一個複雜對象的創建與它的表示分離,使得同樣的創建過程可以創建不同的表示。

需要建造者,還需要一個指揮者,負責整體的構建算法,也就是如何去組合產品。

應用:一個類的各個組成部分的具體實現類或者算法經常變化,但是將它們組合在一起的算法卻相對穩定。提供一種封裝機制將穩定的組合算法於易變的各個組成部分隔離開來。

  • 原型模式

用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
簡單的說,就是從一個對象再創建另外一個可定製的對象,而且不需要知道任何創建的細節。

應用:如Object中的clone方法,需要該類實現Cloneable接口,注意這是淺表複製。如果想實現深表複製,可以將引用的對象也實現Cloneable接口,重寫clone方法;或採用序列化,也就是採用流的方式讀入當前對象的二進制輸入,再寫出二進制數據對應的對象。

/* 寫入當前對象的二進制流 */  
    ByteArrayOutputStream bos = new ByteArrayOutputStream();  
    ObjectOutputStream oos = new ObjectOutputStream(bos);  
    oos.writeObject(this);  
    /* 讀出二進制流產生的新對象 */  
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
    ObjectInputStream ois = new ObjectInputStream(bis);  
    return ois.readObject();

結構型模式

  • 代理模式

爲其他對象提供一種代理,以控制對這個對象的訪問。
代理對象和被代理對象需要實現相同的接口,這樣代理類才能完成代理工作。

  • 裝飾器模式

動態的給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更加靈活。

裝飾對象和被裝飾對象需要實現共同的接口,這樣可以層層裝飾。
在客戶端創建被裝飾的對象,然後作爲構造參數傳給裝飾對象。

應用:當系統需要新的功能,向舊的類中添加新的代碼。
Java的IO中,BufferedReader(new InputStreamReader(System.in)就是一個裝飾模式。

裝飾模式與代理模式的區別:裝飾模式關注於在一個對象上動態的添加方法,代理模式關注於控制對象的訪問。對於客戶端來說,代理模式中,客戶端不需要知道被代理對象的信息,被代理的對象是在代理類中完成了一個實例的創建。而裝飾模式中,需要在客戶端中將原始對象作爲一個參數傳給裝飾者模式。

  • 適配器模式

將一個類的接口轉換成用戶希望的另外一種接口,使得原本由於接口不兼容而不能一起工作的類可以一起工作。

適配器模式主要是希望複用一些現有的類,但接口與複用環境要求不一致的情況。

應用:系統的數據和行爲都正確時,但是接口不符合,應該用適配器。

  • 外觀模式

爲子系統的一組接口提供一致的界面,此模式定義了一個高層的接口,這個接口使得這一子系統更加容易使用。

應用:比如在MVC架構中,Action層 Service層和Dao層就是外觀模式。Service層中的方法可能會需要多個Dao層中的方法結合使用,這樣將這些方法封裝起來,向Action層提供一系列簡單的接口,使得Action層的代碼更加簡潔和清晰。
Tomcat中也使用了外觀模式,Tomcat中的每個組件都要相互通信,但是不能將自己內部的數據過多的暴露給其他組件,使用外觀模式進行隔離數據。實際上傳遞的是RequestFacade和ResponseFacade對象,只提供外部程序感興趣的方法。

外觀模式與代理模式的區別:
代理模式是代理一個單一的對象,而外觀模式代表一個子系統。
組合模式
將對象組合成樹形結構以表示 部分—整體的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。

對於客戶端來說,無需區分是操作的是樹枝對象還是樹葉對象。

應用:文件系統

  • 享元模式

運用共享技術有效的支持大量細粒度的對象。

實現對象的共享,當系統中對象多的時候可以減少內存的開銷,通常與工廠模式一起使用。

FlyWeightFactory負責創建和管理享元單元,當一個客戶端請求時,工廠需要檢查當前對象池中是否有符合條件的對象,如果有,則返回已經存在的對象,如果沒有,則創建一個新的對象。

應用:String類型就是享元模式,對象一旦創建,就不能改變,存放於常量池中。
數據庫連接池,url driverClassName username password dbname這些對於每個連接來說都是一樣的,所以適合用享元模式處理。通過連接池的管理,實現了數據庫連接的共享,不需要每一次都創建新的連接,節省了數據庫重新創建的開銷,提升了系統的性能。

  • 橋接模式

將抽象部分與它的實現部分分離,使它們都可以獨立的變化。

應用:在JDBC中,使用了橋接模式。對於應用程序而言,只要選擇不同的驅動方式,就可以讓程序操作不同的數據庫,而無需更改應用程序,對於數據庫而言,爲數據庫實現不同的驅動程序,並不會影響應用程序。

行爲型模式

  • 觀察者模式

也是發佈—訂閱模式,是一種一對多的依賴關係。讓多個觀察者對象同時監聽某一個主題對象,這個主題對象發生變化時,會通知所有觀察者對象,使得它們可以自動更新自己。

將一個系統分割成一系列相互合作的類有不好的作用,那就是需要維護相關對象間的一致性。不希望爲了維護一致性而使各類緊密耦合,這樣會給維護 擴展 重用帶來不變。

應用:util庫中有Observale和Observer接口,被觀察對象集成Observable類,Watcher對象實現Observer接口。

Spring中的事件驅動模型是觀察者模式的一個典型應用。

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp;
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
    public final long getTimestamp() {
        return this.timestamp;
    }
}

ApplicationEvent繼承自jdk的EventObject,所有的事件都需要繼承ApplicationEvent,並且通過source得到事件源.該類的實現類ApplicationContextEvent表示ApplicaitonContext的容器事件.

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}

ApplicationListener繼承自jdk的EventListener,所有的監聽器都要實現這個接口,這個接口只有一個onApplicationEvent()方法,該方法接受一個ApplicationEvent或其子類對象作爲參數,在方法體中,可以通過不同對Event類的判斷來進行相應的處理.當事件觸發時所有的監聽器都會收到消息,如果你需要對監聽器的接收順序有要求,可是實現該接口的一個實現SmartApplicationListener,通過這個接口可以指定監聽器接收事件的順序.

事件機制需要事件源 事件 事件監聽器。ApplicationEvent相當於事件, ApplicationListener相當於事件監聽器,事件源是ApplicationContext。ApplicationContext是spring中的全局容器,負責讀取bean的配置文檔,管理bean的加載,維護bean之間的依賴關係。ApplicationContext作爲一個事件源,需要顯示的調用publishEvent方法,傳入一個ApplicationEvent的實現類對象作爲參數,每當ApplicationContext發佈ApplicationEvent時,所有的ApplicationListener就會被自動觸發。
ApplicationContext接口實現了ApplicationEventPublisher接口,裏面有一個發佈事件的方法:

public interface ApplicationEventPublisher {
void publishEvent(ApplicationEvent event);
}

我們常用的ApplicationContext都繼承了AbstractApplicationContext,像我們平時常見的ClassPathXmlApplicationContext、XmlWebApplicationContex也都是繼承了它,AbstractApplicationcontext是ApplicationContext接口的抽象實現類,在該類中實現了publishEvent方法。

public void publishEvent(ApplicationEvent event) {
        Assert.notNull(event, "Event must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Publishing event in " + getDisplayName() + ": " + event);
        }
        getApplicationEventMulticaster().multicastEvent(event);
        if (this.parent != null) {
            this.parent.publishEvent(event);
        }
    }

在這個方法中,我們看到了一個getApplicationEventMulticaster().這就要牽扯到另一個類ApplicationEventMulticaster.
ApplicationEventMulticaster屬於事件廣播器,作用就是把ApplicationContext發佈的Event廣播給所有的監聽器。
在AbstractApplicationcontext中有一個applicationEventMulticaster的成員變量,提供了監聽器Listener的註冊方法.

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext, DisposableBean {

  private ApplicationEventMulticaster applicationEventMulticaster;
  protected void registerListeners() {
        // Register statically specified listeners first.
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }
        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let post-processors apply to them!
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String lisName : listenerBeanNames) {
            getApplicationEventMulticaster().addApplicationListenerBean(lisName);
        }
    }
}
  • 責任鏈模式

使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合。將這些對象鏈接成一條鏈,並沿着這條鏈傳遞該請求,直到一個對象處理爲止。

好處:請求是沿着鏈傳遞,直至有一個具體的處理者對象對其進行處理。這使得接收者和發送者都沒有對方明確的信息,並且鏈中的對象也不知道鏈的結構。職責鏈可以簡化對象之間的相互連接,僅需保持一個指向其後繼者的調用,而不需要保持它所有的候選接收者的引用。

應用:Tomcat中的Filter使用了責任鏈模式。
Tomcat的容器設置也是責任鏈模式。Engine-Host-Context-Wrapper都是通過一個鏈傳遞請求。

  • 模板方法模式

定義了一個操作中算法的骨架,而將一些步驟延遲到子類。模板方法使得子類可以不改變一個算法的結構就可以重新定義該算法的某些特定的步驟。

將不變的行爲搬移到超類中,去除子類中的重複代碼來體現它的優勢。

應用:HttpServlet提供一個service方法,這個方法調用了7個do方法中的一個或者幾個,完成對客戶端的響應。這些do方法要求HttpServlet具體子類去實現。

  • 策略模式

定義了算法家族,分別封裝起來,讓它們之間可以相互替換,此模式讓算法的變化不會影響到使用算法的用戶。

這些算法都是完成相同的工作,只是實現不同。

應用:需要在不同的時間點,應用不用的業務規則。(比如打折促銷各種優惠手段)
迭代子模式
提供一種方法順序的訪問一個聚合對象中各個元素,而不是暴露該對象的內部表示。

應用:當需要訪問一個聚集對象時,而且不管這些對象是什麼就需要遍歷的時候。
Java中的集合。

  • 命令模式

將一個請求封裝爲一個對象,從而使你可以用不同的請求對客戶 進行參數化,對請求排隊或者記錄請求日誌時,以及支持撤銷的操作。

命令模式把發出命令的責任和執行命令的責任分割開來,委派給不同的對象。
優點:比較容易的設計一個請求隊列。在需要的情況下,可以比較容易的將命令記入日誌。
允許接收請求的一方決定是否要否決請求。比較容易的實現對請求的撤銷和重做。

  • 狀態模式

當一個對象的內存狀態改變時,允許改變其行爲。這個對象看起來是像改變了類。

當控制一個對象狀態轉換的條件表達式過於複雜時的情況。把狀態的判斷邏輯轉移到不同狀態的一系列類當中,可以把複雜的判斷邏輯簡化。

應用:當一個對象的行爲取決於它的狀態時,並且它必須在運行時刻根據它的狀態改變它的行爲時,可以考慮使用。

  • 備忘錄模式

在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到原先保存的狀態。

應用:適應於功能比較複雜,但需要維護或記錄屬性歷史的類。(遊戲進度)

  • 訪問者模式

表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下,定義作用於這些元素的新操作。

應用:適應於數據結構相對穩定的系統。它把數據結構和作用於結構上的操作之間的耦合解脫開,使得操作集合可以相對的自由的演化。目的是把處理從數據結構中分離出來。如果系統中有比較穩定的數據結構,又有易於變化的算法,用訪問者模式。

優點:增加新的操作很容易,只需要增加一個新的訪問者。

  • 中介者模式

用一箇中介對象來封裝一系列的對象交互。中介者使各對象不需要顯示的相互調用,從而使其耦合鬆散,而且可以獨立的改變它們之間的交互。

中介對象需要知道所有的具體同事類,從具體同事類中接收消息,向具體同事發出命令。
具體同事類,每個具體同事只知道自己的行爲,而不瞭解其他同事類的情況,但它們都認識中介對象。

優點:中介者模式減少了各個Colleage的耦合,使得可以獨立的改變和複用各個Colleague類和Mediator。
缺點:交互的複雜性變爲了中介者的複雜性,這使得中介者變得比任何一個類都複雜。

應用:應用於一組對象以定義良好但是複雜的方式進行通信的場合。以及想定製一個分佈在多個類中而又不想生成太多子類的場合。

中介者模式與代理模式:
代理模式是一對一,這個代理只能代表一個對象。只能代理一方,比如PB是B的代理,A能夠通過PB訪問B,但是B不能通過PB訪問A。(比如手機代理,我們只能通過手機代理去買手機)
中介者模式是多對多,這些被管理的對象之間都可以通信,它們的業務關係應該是交互在一起的。A可以通過中介訪問B,B也能夠通過中介訪問A。(比如房屋中介,中介者有房源的信息也有客戶的信息,可以雙向進行通信)

  • 解釋器模式

給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。

應用:如果一種特定類型的問題發生的頻率足夠高,那麼就有可能值得將該問題的實例表述爲一個簡單語言中的句子。這樣就可以構建一個解釋器,該解釋器通過解釋這些句子來解決該問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章