1. 程式人生 > >23種設計模式的總結~以及區別、應用

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。(比如房屋中介,中介者有房源的資訊也有客戶的資訊,可以雙向進行通訊)

  • 直譯器模式

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

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