天天看點

23種設計模式彙總概述

設計模式(Design pattern)是一套被反複使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人了解、保證代碼可靠性。 毫無疑問,設計模式于己于他人于系統都是多赢的,設計模式使代碼編制真正工程化,設計模式是軟體工程的基石,如同大廈的一塊塊磚石一樣。項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。

設計模式分類:

建立型模式:對象執行個體化的模式,建立型模式用于解耦對象的執行個體化過程。

結構型模式:把類或對象結合在一起形成一個更大的結構。

行為型模式:類和對象如何互動,及劃分責任和算法。

23種設計模式彙總概述

建立型模式

單例模式(Singleton Pattern)

定義:Ensure a class has only one instance, and provide a global point of access to it.(確定某一個類隻有一個執行個體,而且自行執行個體化并向整個系統提供這個執行個體。)

餓漢模式:(在單例類定義的時候(即在main函數之前)就進行執行個體化,是線程安全的)

/**
 * 單例模式(線程安全)
 * @author zjq
 */
public class Singleton {
    private static final Singleton singleton = new Singleton();
    /**限制産生多個對象*/
    private Singleton(){
    }

    /**
     * 通過該方法獲得執行個體對象
     * @return
     */
    public static Singleton getSingleton(){
        return singleton;
    }

    /**
     * 類中其他方法,盡量是static
     */
    public static void doSomething(){
    }
}
           

使用場景:

  • 要求生成唯一序列号的環境;
  • 在整個項目中需要一個共享通路點或共享資料,例如一個Web頁面上的計數器,可以不用把每次重新整理都記錄到資料庫中,使用單例模式保持計數器的值,并確定是線程安全的;
  • 建立一個對象需要消耗的資源過多,如要通路IO和資料庫等資源;
  • 需要定義大量的靜态常量和靜态方法(如工具類)的環境,可以采用單例模式(當然,也可以直接聲明為static的方式)。

懶漢模式:(在第一次用到類執行個體的時候才會去執行個體化,線程不安全)

public class Singleton {
    private static Singleton singleton = null;
    //限制産生多個對象
    private Singleton(){
    }
    //通過該方法獲得執行個體對象
    public static Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}
           

解決辦法:

在getSingleton方法前加synchronized關鍵字,也可以在getSingleton方法内增加synchronized來實作。最優的辦法是如通用代碼那樣寫。

工廠模式(Factory Pattern)

定義:Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.(定義一個用于建立對象的接口,讓子類決定執行個體化哪一個類。工廠方法使一個類的執行個體化延遲到其子類。)

Product為抽象産品類負責定義産品的共性,實作對事物最抽象的定義;

Creator為抽象建立類,也就是抽象工廠,具體如何建立産品類是由具體的實作工廠ConcreteCreator完成的。

簡單工廠模式:

一個子產品僅需要一個工廠類,沒有必要把它産生出來,使用靜态的方法

多個工廠類:

每個人種(具體的産品類)都對應了一個建立者,每個建立者獨立負責建立對應的産品對象,非常符合單一職責原則

代替單例模式:

單例模式的核心要求就是在記憶體中隻有一個對象,通過工廠方法模式也可以隻在記憶體中生産一個對象

延遲初始化:

ProductFactory負責産品類對象的建立工作,并且通過prMap變量産生一個緩存,對需要再次被重用的對象保留

使用場景:jdbc連接配接資料庫,硬體通路,降低對象的産生和銷毀

結構類圖:

23種設計模式彙總概述

抽象工廠模式(Abstract Factory Pattern)

定義:Provide an interface for creating families of related or dependent objects without specifying their concrete classes.(為建立一組相關或互相依賴的對象提供一個接口,而且無須指定它們的具體類。)

抽象工廠模式通用類圖:

23種設計模式彙總概述

抽象工廠類代碼:

public abstract class AbstractCreator {
    //建立A産品家族
    public abstract AbstractProductA createProductA();
    //建立B産品家族
    public abstract AbstractProductB createProductB();
}
           

使用場景:

一個對象族(或是一組沒有任何關系的對象)都有相同的限制。

涉及不同作業系統的時候,都可以考慮使用抽象工廠模式。

原型模式(Prototype Pattern)

定義:Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.(用原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象。)

原型模式類圖:

23種設計模式彙總概述

原型模式通用代碼:

public class PrototypeClass implements Cloneable{
    //覆寫父類Object方法
    @Override
    public PrototypeClass clone(){
        PrototypeClass prototypeClass = null;
        try {
            prototypeClass = (PrototypeClass)super.clone();
        } catch (CloneNotSupportedException e) {
            //異常處理
        }
        return prototypeClass;
    }
}
           

原型模式實際上就是實作Cloneable接口,重寫clone()方法。

使用原型模式的優點:

  • 性能優良

原型模式是在記憶體二進制流的拷貝,要比直接new一個對象性能好很多,特别是要在一個循環體内産生大量的對象時,原型模式可以更好地展現其優點。

  • 逃避構造函數的限制

這既是它的優點也是缺點,直接在記憶體中拷貝,構造函數是不會執行的(參見13.4節)。

使用場景:

  • 資源優化場景

類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等。

  • 性能和安全要求的場景

通過new産生一個對象需要非常繁瑣的資料準備或通路權限,則可以使用原型模式。

  • 一個對象多個修改者的場景

一個對象需要提供給其他對象通路,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。

淺拷貝和深拷貝:

淺拷貝:Object類提供的方法clone隻是拷貝本對象,其對象内部的數組、引用對象等都不拷貝,還是指向原生對象的内部元素位址,這種拷貝就叫做淺拷貝,其他的原始類型比如int、long、char、string(當做是原始類型)等都會被拷貝。

💡注意:使用原型模式時,引用的成員變量必須滿足兩個條件才不會被拷貝:一是類的成員變量,而不是方法内變量;二是必須是一個可變的引用對象,而不是一個原始類型或不可變對象。

深拷貝:對私有的類變量進行獨立的拷貝

如:thing.arrayList = (ArrayList<String>)this.arrayList.clone();

建造者模式(Builder Pattern)

定義:Separate the construction of a complex object from its representation so that the same construction process can create different representations.(将一個複雜對象的建構與它的表示分離,使得同樣的建構過程可以建立不同的表示。)

建造者模式類圖:

23種設計模式彙總概述
  • Product産品類

通常是實作了模闆方法模式,也就是有模闆方法和基本方法,例子中的BenzModel和BMWModel就屬于産品類。

  • Builder抽象建造者

規範産品的組建,一般是由子類實作。例子中的CarBuilder就屬于抽象建造者。

  • ConcreteBuilder具體建造者

實作抽象類定義的所有方法,并且傳回一個組建好的對象。例子中的BenzBuilder和BMWBuilder就屬于具體建造者。

  • Director導演類

負責安排已有子產品的順序,然後告訴Builder開始建造

使用場景:

  • 相同的方法,不同的執行順序,産生不同的事件結果時,可以采用建造者模式。
  • 多個部件或零件,都可以裝配到一個對象中,但是産生的運作結果又不相同時,則可以使用該模式。
  • 産品類非常複雜,或者産品類中的調用順序不同産生了不同的效能,這個時候使用建造者模式非常合适。

建造者模式與工廠模式的不同:

建造者模式最主要的功能是基本方法的調用順序安排,這些基本方法已經實作了,順序不同産生的對象也不同;

工廠方法則重點是建立,建立零件是它的主要職責,組裝順序則不是它關心的。

結構型模式

擴充卡模式(Adapter Pattern)

定義:Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.(将一個類的接口變換成用戶端所期待的另一種接口,進而使原本因接口不比對而無法在一起工作的兩個類能夠在一起工作。)

擴充卡模式類圖:

23種設計模式彙總概述

類擴充卡:

  • Target目标角色

該角色定義把其他類轉換為何種接口,也就是我們的期望接口,例子中的IUserInfo接口就是目标角色。

  • Adaptee源角色

你想把誰轉換成目标角色,這個“誰”就是源角色,它是已經存在的、運作良好的類或對象,經過擴充卡角色的包裝,它會成為一個嶄新、靓麗的角色。

  • Adapter擴充卡角色

擴充卡模式的核心角色,其他兩個角色都是已經存在的角色,而擴充卡角色是需要建立立的,它的職責非常簡單:把源角色轉換為目标角色,怎麼轉換?通過繼承或是類關聯的方式。

使用場景:

你有動機修改一個已經投産中的接口時,擴充卡模式可能是最适合你的模式。比如系統擴充了,需要使用一個已有或建立立的類,但這個類又不符合系統的接口,怎麼辦?使用擴充卡模式,這也是我們例子中提到的。

💡注意事項:

詳細設計階段不要考慮使用擴充卡模式,使用主要場景為擴充應用中。

對象擴充卡和類擴充卡的差別:

類擴充卡是類間繼承,對象擴充卡是對象的合成關系,也可以說是類的關聯關系,這是兩者的根本差別。(實際項目中對象擴充卡使用到的場景相對比較多)。

橋梁/橋接模式(Bridge Pattern)

定義:Decouple an abstraction from its implementation so that the two can vary independently.(将抽象和實作解耦,使得兩者可以獨立地變化。)

橋梁模式類圖:

23種設計模式彙總概述
  • Abstraction——抽象化角色

它的主要職責是定義出該角色的行為,同時儲存一個對實作化角色的引用,該角色一般是抽象類。

  • Implementor——實作化角色

它是接口或者抽象類,定義角色必需的行為和屬性。

  • RefinedAbstraction——修正抽象化角色

它引用實作化角色對抽象化角色進行修正。

  • ConcreteImplementor——具體實作化角色

它實作接口或抽象類定義的方法和屬性。

使用場景:

  • 不希望或不适用使用繼承的場景
  • 接口或抽象類不穩定的場景
  • 重用性要求較高的場景

💡注意:

發現類的繼承有N層時,可以考慮使用橋梁模式。橋梁模式主要考慮如何拆分抽象和實作。

設計原則:

  • Single Responsibility Principle:單一職責原則

單一職責原則有什麼好處:

  • 類的複雜性降低,實作什麼職責都有清晰明确的定義;
  • 可讀性提高,複雜性降低,那當然可讀性提高了;
  • 可維護性提高,可讀性提高,那當然更容易維護了;
  • 變更引起的風險降低,變更是必不可少的,如果接口的單一職責做得好,一個接口修改隻對相應的實作類有影響,對其他的接口無影響,這對系統的擴充性、維護性都有非常大的幫助。

ps:接口一定要做到單一職責,類的設計盡量做到隻有一個原因引起變化。

單一職責原則提出了一個編寫程式的标準,用“職責”或“變化原因”來衡量接口或類設計得是否優良,但是“職責”和“變化原因”都是不可度量的,因項目而異,因環境而異。

  • Liskov Substitution Principle:裡氏替換原則

定義:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

(所有引用基類的地方必須能透明地使用其子類的對象。)

通俗點講,隻要父類能出現的地方子類就可以出現,而且替換為子類也不會産生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,父類未必就能适應。

定義中包含的四層含義:

  1. 子類必須完全實作父類的方法
  2. 子類可以有自己的個性
  3. 覆寫或實作父類的方法時輸入參數可以被放大

如果父類的輸入參數類型大于子類的輸入參數類型,會出現父類存在的地方,子類未必會存在,因為一旦把子類作為參數傳入,調用者很可能進入子類的方法範疇。

  1. 覆寫或實作父類的方法時輸出結果可以被縮小

父類的一個方法的傳回值是一個類型T,子類的相同方法(重載或覆寫)的傳回值為S,那麼裡氏替換原則就要求S必須小于等于T,也就是說,要麼S和T是同一個類型,要麼S是T的子類。

  • Interface Segregation Principle:接口隔離原則

接口分為兩種:

執行個體接口(Object Interface):Java中的類也是一種接口

類接口(Class Interface): Java中經常使用Interface關鍵字定義的接口

隔離:建立單一接口,不要建立臃腫龐大的接口;即接口要盡量細化,同時接口中的方法要盡量少。

接口隔離原則與單一職責原則的不同:接口隔離原則與單一職責的審視角度是不相同的,單一職責要求的是類和接口職責單一,注重的是職責,這是業務邏輯上的劃分,而接口隔離原則要求接口的方法盡量少。

  • Dependence Inversion Principle:依賴倒置原則

原始定義:

①高層子產品不應該依賴低層子產品,兩者都應該依賴其抽象;

②抽象不應該依賴細節(實作類);

③細節應該依賴抽象。

依賴倒置原則在java語言中的展現:

①子產品間的依賴通過抽象發生,實作類之間不發生直接的依賴關系,其依賴關系是通過接口或抽象類産生的;

②接口或抽象類不依賴于實作類;

③實作類依賴接口或抽象類。

依賴的三種寫法:

①構造函數傳遞依賴對象(構造函數注入)

②Setter方法傳遞依賴對象(setter依賴注入)

③接口聲明依賴對象(接口注入)

使用原則:

依賴倒置原則的本質就是通過抽象(接口或抽象類)使各個類或子產品的實作彼此獨立,不互相影響,實作子產品間的松耦合,我們怎麼在項目中使用這個規則呢?隻要遵循以下的幾個規則就可以:

①每個類盡量都有接口或抽象類,或者抽象類和接口兩者都具備

②變量的表面類型盡量是接口或者是抽象類

③任何類都不應該從具體類派生(隻要不超過兩層的繼承是可以忍受的)

④盡量不要複寫基類的方法

⑤結合裡氏替換原則使用

  • Open Closed Principle:開閉原則

定義:軟體實體應該對擴充開放,對修改關閉。

其含義是說一個軟體實體應該通過擴充來實作變化,而不是通過修改已有的代碼來實作變化。

軟體實體:項目或軟體産品中按照一定的邏輯規則劃分的子產品、抽象和類、方法。

變化的三種類型:

①邏輯變化

隻變化一個邏輯,而不涉及其他子產品,比如原有的一個算法是ab+c,現在需要修改為ab*c,可以通過修改原有類中的方法的方式來完成,前提條件是所有依賴或關聯類都按照相同的邏輯處理。

②子子產品變化

一個子產品變化,會對其他的子產品産生影響,特别是一個低層次的子產品變化必然引起高層子產品的變化,是以在通過擴充完成變化時,高層次的子產品修改是必然的。

③可見視圖變化

可見視圖是提供給客戶使用的界面,如JSP程式、Swing界面等,該部分的變化一般會引起連鎖反應(特别是在國内做項目,做歐美的外包項目一般不會影響太大)。可以通過擴充來完成變化,這要看我們原有的設計是否靈活。

組合模式(Composite Pattern)

定義:Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.(将對象組合成樹形結構以表示“部分-整體”的層次結構,使得使用者對單個對象群組合對象的使用具有一緻性。)

組合模式類圖:

23種設計模式彙總概述
  • Component抽象構件角色

定義參加組合對象的共有方法和屬性,可以定義一些預設的行為或屬性,比如我們例子中的getInfo就封裝到了抽象類中。

  • Leaf葉子構件

葉子對象,其下再也沒有其他的分支,也就是周遊的最小機關。

  • Composite樹枝構件

樹枝對象,它的作用是組合樹枝節點和葉子節點形成一個樹形結構。

樹枝構件的通用代碼:

public class Composite extends Component {
    //構件容器
    private ArrayList<Component> componentArrayList = new ArrayList<Component>();
    //增加一個葉子構件或樹枝構件
    public void add(Component component){
        this.componentArrayList.add(component);
    }
    //删除一個葉子構件或樹枝構件
    public void remove(Component component){
        this.componentArrayList.remove(component);
    }
    //獲得分支下的所有葉子構件和樹枝構件
    public ArrayList<Component> getChildren(){
        return this.componentArrayList;
    }
}
           

使用場景:

  • 維護和展示部分-整體關系的場景,如樹形菜單、檔案和檔案夾管理。
  • 從一個整體中能夠獨立出部分子產品或功能的場景。

💡注意:

隻要是樹形結構,就考慮使用組合模式。

裝飾者模式(Decorator Pattern)

定義:Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(動态地給一個對象添加一些額外的職責。就增加功能來說,裝飾模式相比生成子類更為靈活。)

裝飾器模式類圖:

23種設計模式彙總概述
  • Component抽象構件

Component是一個接口或者是抽象類,就是定義我們最核心的對象,也就是最原始的對象,如上面的成績單。

注意:在裝飾模式中,必然有一個最基本、最核心、最原始的接口或抽象類充當Component抽象構件。

  • ConcreteComponent 具體構件

ConcreteComponent是最核心、最原始、最基本的接口或抽象類的實作,你要裝飾的就是它。

  • Decorator裝飾角色

一般是一個抽象類,做什麼用呢?實作接口或者抽象方法,它裡面可不一定有抽象的方法呀,在它的屬性裡必然有一個private變量指向Component抽象構件。

  • 具體裝飾角色

ConcreteDecoratorA和ConcreteDecoratorB是兩個具體的裝飾類,你要把你最核心的、最原始的、最基本的東西裝飾成其他東西,上面的例子就是把一個比較平庸的成績單裝飾成家長認可的成績單。

使用場景:

  • 需要擴充一個類的功能,或給一個類增加附加功能。
  • 需要動态地給一個對象增加功能,這些功能可以再動态地撤銷。
  • 需要為一批的兄弟類進行改裝或加裝功能,當然是首選裝飾模式。

外觀/門面模式(Facade Pattern)

定義:Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.(要求一個子系統的外部與其内部的通信必須通過一個統一的對象進行。門面模式提供一個高層次的接口,使得子系統更易于使用。)

門面模式類圖:

23種設計模式彙總概述
  • Facade門面角色 用戶端可以調用這個角色的方法。此角色知曉子系統的所有功能和責任。一般情況下,本角色會将所有從用戶端發來的請求委派到相應的子系統去,也就說該角色沒有實際的業務邏輯,隻是一個委托類。 -subsystem子系統角色 可以同時有一個或者多個子系統。每一個子系統都不是一個單獨的類,而是一個類的集合。子系統并不知道門面的存在。對于子系統而言,門面僅僅是另外一個用戶端而已。

使用場景:

  • 為一個複雜的子產品或子系統提供一個供外界通路的接口
  • 子系統相對獨立——外界對子系統的通路隻要黑箱操作即可
  • 預防低水準人員帶來的風險擴散

💡注意:

  • 一個子系統可以有多個門面
  • 門面不參與子系統内的業務邏輯

享元模式(Flyweight Pattern)

定義:Use sharing to support large numbers of fine-grained objects efficiently.(使用共享對象可有效地支援大量的細粒度的對象。)

享元模式類圖:

23種設計模式彙總概述

對象的資訊分為兩個部分:内部狀态(intrinsic)與外部狀态(extrinsic)。

  • 内部狀态

内部狀态是對象可共享出來的資訊,存儲在享元對象内部并且不會随環境改變而改變。

  • 外部狀态

外部狀态是對象得以依賴的一個标記,是随環境改變而改變的、不可以共享的狀态。

  • Flyweight——抽象享元角色

它簡單地說就是一個産品的抽象類,同時定義出對象的外部狀态和内部狀态的接口或實作。

  • ConcreteFlyweight——具體享元角色

具體的一個産品類,實作抽象角色定義的業務。該角色中需要注意的是内部狀态處理應該與環境無關,不應該出現一個操作改變了内部狀态,同時修改了外部狀态,這是絕對不允許的。

  • unsharedConcreteFlyweight——不可共享的享元角色

不存在外部狀态或者安全要求(如線程安全)不能夠使用共享技術的對象,該對象一般不會出現在享元工廠中。

  • FlyweightFactory——享元工廠

職責非常簡單,就是構造一個池容器,同時提供從池中獲得對象的方法。

享元工廠的代碼:

public class FlyweightFactory {
    //定義一個池容器
    private static HashMap<String,Flyweight> pool= new HashMap<String,Flyweight>();
    //享元工廠
    public static Flyweight getFlyweight(String Extrinsic){
        //需要傳回的對象
        Flyweight flyweight = null;
        //在池中沒有該對象
        if(pool.containsKey(Extrinsic)){
            flyweight = pool.get(Extrinsic);
        }else{
            //根據外部狀态建立享元對象
            flyweight = new ConcreteFlyweight1(Extrinsic);
            //放置到池中
            pool.put(Extrinsic, flyweight);
        }
        return flyweight;
    }
}
           

使用場景:

  • 系統中存在大量的相似對象。
  • 細粒度的對象都具備較接近的外部狀态,而且内部狀态與環境無關,也就是說對象沒有特定身份。
  • 需要緩沖池的場景。

💡注意:

  • 享元模式是線程不安全的,隻有依靠經驗,在需要的地方考慮一下線程安全,在大部分場景下不用考慮。對象池中的享元對象盡量多,多到足夠滿足為止。
  • 性能安全:外部狀态最好以java的基本類型作為标志,如String,int,可以提高效率。

代理模式(Proxy Pattern)

定義:Provide a surrogate or placeholder for another object to control access to it.(為其他對象提供一種代理以控制對這個對象的通路。)

代理模式類圖:

23種設計模式彙總概述
  • Subject抽象主題角色

抽象主題類可以是抽象類也可以是接口,是一個最普通的業務類型定義,無特殊要求。

  • RealSubject具體主題角色

也叫做被委托角色、被代理角色。它才是冤大頭,是業務邏輯的具體執行者。

  • Proxy代理主題角色

也叫做委托類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制委托給真實主題角色實作,并且在真實主題角色處理完畢前後做預處理和善後處理工作。

普通代理和強制代理:

普通代理就是我們要知道代理的存在,也就是類似的GamePlayerProxy這個類的存在,然後才能通路;

強制代理則是調用者直接調用真實角色,而不用關心代理是否存在,其代理的産生是由真實角色決定的。

普通代理:

在該模式下,調用者隻知代理而不用知道真實的角色是誰,屏蔽了真實角色的變更對高層子產品的影響,真實的主題角色想怎麼修改就怎麼修改,對高層次的子產品沒有

任何的影響,隻要你實作了接口所對應的方法,該模式非常适合對擴充性要求較高的場合。

強制代理:

強制代理的概念就是要從真實角色查找到代理角色,不允許直接通路真實角色。高層子產品隻要調用getProxy就可以通路真實角色的所有方法,它根本就不需要産生一個代理出來,代理的管理已經由真實角色自己完成。

動态代理:

根據被代理的接口生成所有的方法,也就是說給定一個接口,動态代理會宣稱“我已經實作該接口下的所有方法了”。

兩條獨立發展的線路。動态代理實作代理的職責,業務邏輯Subject實作相關的邏輯功能,兩者之間沒有必然的互相耦合的關系。通知Advice從另一個切面切入,最終在高層子產品也就是Client進行耦合,完成邏輯的封裝任務。

動态代理的意圖:橫切面程式設計,在不改變我們已有代碼結構的情況下增強或控制對象的行為。

首要條件:被代理的類必須要實作一個接口。

行為型模式

通路者模式(Visitor Pattern)

定義:Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封裝一些作用于某種資料結構中的各元素的操作,它可以在不改變資料結構的前提下定義作用于這些元素的新的操作。)

通路者模式類圖:

23種設計模式彙總概述
  • Visitor——抽象通路者

抽象類或者接口,聲明通路者可以通路哪些元素,具體到程式中就是visit方法的參數定義哪些對象是可以被通路的。

  • ConcreteVisitor——具體通路者

它影響通路者通路到一個類後該怎麼幹,要做什麼事情。

  • Element——抽象元素

接口或者抽象類,聲明接受哪一類通路者通路,程式上是通過accept方法中的參數來定義的。

  • ConcreteElement——具體元素

實作accept方法,通常是visitor.visit(this),基本上都形成了一種模式了。

  • ObjectStruture——結構對象

元素産生者,一般容納在多個不同類、不同接口的容器,如List、Set、Map等,在項目中,一般很少抽象出這個角色。

使用場景:

  • 一個對象結構包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴于其具體類的操作,也就說是用疊代器模式已經不能勝任的情景。
  • 需要對一個對象結構中的對象進行很多不同并且不相關的操作,而你想避免讓這些操作“污染”這些對象的類。

模闆方法模式(Template Method Pattern)

定義:Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.(定義一個操作中的算法的架構,而将一些步驟延遲到子類中。使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。)

模闆方法模式類圖:

23種設計模式彙總概述

AbstractClass叫做抽象模闆,它的方法分為兩類:

  • 基本方法

基本方法也叫做基本操作,是由子類實作的方法,并且在模闆方法被調用。

  • 模闆方法

可以有一個或幾個,一般是一個具體方法,也就是一個架構,實作對基本方法的排程,完成固定的邏輯。

💡注意:為了防止惡意的操作,一般模闆方法都加上final關鍵字,不允許被覆寫。

具體模闆:ConcreteClass1和ConcreteClass2屬于具體模闆,實作父類所定義的一個或多個抽象方法,也就是父類定義的基本方法在子類中得以實作

使用場景:

  • 多個子類有公有的方法,并且邏輯基本相同時。
  • 重要、複雜的算法,可以把核心算法設計為模闆方法,周邊的相關細節功能則由各個子類實作。
  • 重構時,模闆方法模式是一個經常使用的模式,把相同的代碼抽取到父類中,然後通過鈎子函數(見“模闆方法模式的擴充”)限制其行為。

政策模式(Strategy Pattern)

定義:Define a family of algorithms,encapsulate each one,and make them interchangeable.(定義一組算法,将每個算法都封裝起來,并且使它們之間可以互換。)

政策模式類圖:

23種設計模式彙總概述
  • Context封裝角色

它也叫做上下文角色,起承上啟下封裝作用,屏蔽高層子產品對政策、算法的直接通路,封裝可能存在的變化。

  • Strategy抽象政策角色

政策、算法家族的抽象,通常為接口,定義每個政策或算法必須具有的方法和屬性。各位看官可能要問了,類圖中的AlgorithmInterface是什麼意思,嘿嘿,algorithm是“運算法則”的意思,結合起來意思就明白了吧。

  • ConcreteStrategy具體政策角色(多個)

實作抽象政策中的操作,該類含有具體的算法。

使用場景:

  • 多個類隻有在算法或行為上稍有不同的場景。
  • 算法需要自由切換的場景。
  • 需要屏蔽算法規則的場景。

💡注意事項:具體政策數量超過4個,則需要考慮使用混合模式

政策模式擴充:政策枚舉

public enum Calculator {
    //加法運算
    ADD("+"){
        public int exec(int a,int b){
        return a+b;
    }
    },
    //減法運算
    SUB("-"){
        public int exec(int a,int b){
        return a - b;
    }
    };
    String value = "";
    //定義成員值類型
    private Calculator(String _value){
        this.value = _value;
    }
    //獲得枚舉成員的值
    public String getValue(){
        return this.value;
    }
    //聲明一個抽象函數
    public abstract int exec(int a,int b);
}
           

定義:

  • 它是一個枚舉。
  • 它是一個濃縮了的政策模式的枚舉。

💡注意:

受枚舉類型的限制,每個枚舉項都是public、final、static的,擴充性受到了一定的限制,是以在系統開發中,政策枚舉一般擔當不經常發生變化的角色。

緻命缺陷:

所有的政策都需要暴露出去,由用戶端決定使用哪一個政策。

狀态模式(State Pattern)

定義:Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(當一個對象内在狀态改變時允許其改變行為,這個對象看起來像改變了其類。)

狀态模式類圖:

23種設計模式彙總概述
  • State——抽象狀态角色

接口或抽象類,負責對象狀态定義,并且封裝環境角色以實作狀态切換。

  • ConcreteState——具體狀态角色

每一個具體狀态必須完成兩個職責:本狀态的行為管理以及趨向狀态處理,通俗地說,就是本狀态下要做的事情,以及本狀态如何過渡到其他狀态。

  • Context——環境角色

定義用戶端需要的接口,并且負責具體狀态的切換。

使用場景:

  • 行為随狀态改變而改變的場景

這也是狀态模式的根本出發點,例如權限設計,人員的狀态不同即使執行相同的行為結果也會不同,在這種情況下需要考慮使用狀态模式。

  • 條件、分支判斷語句的替代者

💡注意:

狀态模式适用于當某個對象在它的狀态發生改變時,它的行為也随着發生比較大的變化,也就是說在行為受狀态限制的情況下可以使用狀态模式,而且使用時對象的狀态最好不要超過5個。

觀察者模式(Observer Pattern)

定義:Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.(定義對象間一種一對多的依賴關系,使得每當一個對象改變狀态,則所有依賴于它的對象都會得到通知并被自動更新。)

觀察者模式類圖:

23種設計模式彙總概述
  • Subject被觀察者

定義被觀察者必須實作的職責,它必須能夠動态地增加、取消觀察者。它一般是抽象類或者是實作類,僅僅完成作為被觀察者必須實作的職責:管理觀察者并通知觀察者。

  • Observer觀察者

觀察者接收到消息後,即進行update(更新方法)操作,對接收到的資訊進行處理。

  • ConcreteSubject具體的被觀察者

定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。

  • ConcreteObserver具體的觀察者

每個觀察在接收到消息後的處理反應是不同,各個觀察者有自己的處理邏輯。

被觀察者通用代碼:

public abstract class Subject {
    //定義一個觀察者數組
    private Vector<Observer> obsVector = new Vector<Observer>();
    //增加一個觀察者
    public void addObserver(Observer o){
        this.obsVector.add(o);
    }
    //删除一個觀察者
    public void delObserver(Observer o){
        this.obsVector.remove(o);
    }
    //通知所有觀察者
    public void notifyObservers(){
        for(Observer o:this.obsVector){
            o.update();
        }
    }
}
           

使用場景:

  • 關聯行為場景。需要注意的是,關聯行為是可拆分的,而不是“組合”關系。
  • 事件多級觸發場景。
  • 跨系統的消息交換場景,如消息隊列的處理機制。

💡注意:

  • 廣播鍊的問題

在一個觀察者模式中最多出現一個對象既是觀察者也是被觀察者,也就是說消息最多轉發一次(傳遞兩次)。

  • 異步處理問題

觀察者比較多,而且處理時間比較長,采用異步處理來考慮線程安全和隊列的問題。

備忘錄模式(Memento Pattern)

定義:Without violating encapsulation,capture and externalize an object's internal state so that the object can be restored to this state later.(在不破壞封裝性的前提下,捕獲一個對象的内部狀态,并在該對象之外儲存這個狀态。這樣以後就可将該對象恢複到原先儲存的狀态。)

備忘錄模式類圖:

23種設計模式彙總概述
  • Originator發起人角色

記錄目前時刻的内部狀态,負責定義哪些屬于備份範圍的狀态,負責建立和恢複備忘錄資料。

  • Memento備忘錄角色(簡單的javabean)

負責存儲Originator發起人對象的内部狀态,在需要的時候提供發起人需要的内部狀态。

  • Caretaker備忘錄管理者角色(簡單的javabean)

對備忘錄進行管理、儲存和提供備忘錄。

使用場景:

  • 需要儲存和恢複資料的相關狀态場景。
  • 提供一個可復原(rollback)的操作。
  • 需要監控的副本場景中。
  • 資料庫連接配接的事務管理就是用的備忘錄模式。

注意:

  • 備忘錄的生命期
  • 備忘錄的性能

不要在頻繁建立備份的場景中使用備忘錄模式(比如一個for循環中)。

clone方式備忘錄:

  • 發起人角色融合了發起人角色和備忘錄角色,具有雙重功效

多狀态的備忘錄模式

  • 增加了一個BeanUtils類,其中backupProp是把發起人的所有屬性值轉換到HashMap中,友善備忘錄角色存儲。restoreProp方法則是把HashMap中的值傳回到發起人角色中。

BeanUtil工具類代碼:

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;

public class BeanUtils {
    //把bean的所有屬性及數值放入到Hashmap中
    public static HashMap<String, Object> backupProp(Object bean) {
        HashMap<String, Object> result = new HashMap<String, Object>();
        try {
            //獲得Bean描述
            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
            //獲得屬性描述
            PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
            //周遊所有屬性
            for (PropertyDescriptor des : descriptors) {
                //屬性名稱
                String fieldName = des.getName();
                //讀取屬性的方法
                Method getter = des.getReadMethod();
                //讀取屬性值
                Object fieldValue = getter.invoke(bean, new Object[]{});
                if (!fieldName.equalsIgnoreCase("class")) {
                    result.put(fieldName, fieldValue);
                }
            }
        } catch (Exception e) {
        //異常處理
        }
        return result;
    }

    //把HashMap的值傳回到bean中
    public static void restoreProp(Object bean, HashMap<String, Object> propMap) {
        try {
            //獲得Bean描述
            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
            //獲得屬性描述
            PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
            //周遊所有屬性
            for (PropertyDescriptor des : descriptors) {
                //屬性名稱
                String fieldName = des.getName();
                //如果有這個屬性
                if (propMap.containsKey(fieldName)) {
                    //寫屬性的方法
                    Method setter = des.getWriteMethod();
                    setter.invoke(bean, new Object[]{propMap.get(fieldName)});
                }
            }
        } catch (Exception e) {
            //異常處理
            System.out.println("shit");
            e.printStackTrace();
        }
    }
}
           

多備份的備忘錄:略

封裝得更好一點:保證隻能對發起人可讀

  • 建立一個空接口IMemento——什麼方法屬性都沒有的接口,然後在發起人Originator類中建立一個内置類(也叫做類中類)Memento實作IMemento接口,同時也實作自己的業務邏輯。

中介者模式(Mediator Pattern)

定義:Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects from referring to each other explicitly,and it lets you vary their interaction independently.(用一個中介對象封裝一系列的對象互動,中介者使各對象不需要顯示地互相作用,進而使其耦合松散,而且可以獨立地改變它們之間的互動。)

中介者模式類圖:

23種設計模式彙總概述
  • Mediator 抽象中介者角色

抽象中介者角色定義統一的接口,用于各同僚角色之間的通信。

  • Concrete Mediator 具體中介者角色

具體中介者角色通過協調各同僚角色實作協作行為,是以它必須依賴于各個同僚角色。

  • Colleague 同僚角色

每一個同僚角色都知道中介者角色,而且與其他的同僚角色通信的時候,一定要通過中介者角色協作。每個同僚類的行為分為兩種:一種是同僚本身的行為,比如改變對象本身的狀态,處理自己的行為等,這種行為叫做自發行為(Self-Method),與其他的同僚類或中介者沒有任何的依賴;第二種是必須依賴中介者才能完成的行為,叫做依賴方法(Dep-Method)。

通用抽象中介者代碼:

public abstract class Mediator {
    //定義同僚類
    protected ConcreteColleague1 c1;
    protected ConcreteColleague2 c2;

    //通過getter/setter方法把同僚類注入進來
    public ConcreteColleague1 getC1() {
        return c1;
    }

    public void setC1(ConcreteColleague1 c1) {
        this.c1 = c1;
    }

    public ConcreteColleague2 getC2() {
        return c2;
    }

    public void setC2(ConcreteColleague2 c2) {
        this.c2 = c2;
    }

    //中介者模式的業務邏輯
    public abstract void doSomething1();

    public abstract void doSomething2();
}
           

ps:使用同僚類注入而不使用抽象注入的原因是因為抽象類中不具有每個同僚類必須要完成的方法。即每個同僚類中的方法各不相同。

問:為什麼同僚類要使用構造函數注入中介者,而中介者使用getter/setter方式注入同僚類呢?

這是因為同僚類必須有中介者,而中介者卻可以隻有部分同僚類。

使用場景:

中介者模式适用于多個對象之間緊密耦合的情況,緊密耦合的标準是:在類圖中出現了蜘蛛網狀結構,即每個類都與其他的類有直接的聯系。

疊代器模式(Iterator Pattern)

定義:Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.(它提供一種方法通路一個容器對象中各個元素,而又不需暴露該對象的内部細節。)

疊代器模式類圖:

23種設計模式彙總概述
  • Iterator抽象疊代器

抽象疊代器負責定義通路和周遊元素的接口,而且基本上是有固定的3個方法:first()獲得第一個元素,next()通路下一個元素,isDone()是否已經通路到底部(Java叫做hasNext()方法)。

  • ConcreteIterator具體疊代器

具體疊代器角色要實作疊代器接口,完成容器元素的周遊。

  • Aggregate抽象容器

容器角色負責提供建立具體疊代器角色的接口,必然提供一個類似createIterator()這樣的方法,在Java中一般是iterator()方法。

  • Concrete Aggregate具體容器

具體容器實作容器接口定義的方法,建立出容納疊代器的對象。

ps:疊代器模式已經被淘汰,java中已經把疊代器運用到各個聚集類(collection)中了,使用java自帶的疊代器就已經滿足我們的需求了。

解釋器模式(Interpreter Pattern)

定義:Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.(給定一門語言,定義它的文法的一種表示,并定義一個解釋器,該解釋器使用該表示來解釋語言中的句子。)

解釋器模式類圖:

23種設計模式彙總概述
  • AbstractExpression——抽象解釋器

具體的解釋任務由各個實作類完成,具體的解釋器分别由TerminalExpression和Non-terminalExpression完成。

  • TerminalExpression——終結符表達式

實作與文法中的元素相關聯的解釋操作,通常一個解釋器模式中隻有一個終結符表達式,但有多個執行個體,對應不同的終結符。具體到我們例子就是VarExpression類,表達式中的每個終結符都在棧中産生了一個VarExpression對象。

  • NonterminalExpression——非終結符表達式

文法中的每條規則對應于一個非終結表達式,具體到我們的例子就是加減法規則分别對應到AddExpression和SubExpression兩個類。非終結符表達式根據邏輯的複雜程度而增加,原則上每個文法規則都對應一個非終結符表達式。

  • Context——環境角色

具體到我們的例子中是采用HashMap代替。

使用場景:

  • 重複發生的問題可以使用解釋器模式
  • 一個簡單文法需要解釋的場景

💡注意:

盡量不要在重要的子產品中使用解釋器模式,否則維護會是一個很大的問題。在項目中可以使用shell、JRuby、Groovy等腳本語言來代替解釋器模式,彌補Java編譯型語言的不足。

指令模式(Command Pattern)

定義:Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.(将一個請求封裝成一個對象,進而讓你使用不同的請求把用戶端參數化,對請求排隊或者記錄請求日志,可以提供指令的撤銷和恢複功能。)

指令模式類圖:

23種設計模式彙總概述
  • Receive接收者角色

該角色就是幹活的角色,指令傳遞到這裡是應該被執行的,具體到我們上面的例子中就是Group的三個實作類(需求組,美工組,代碼組)。

  • Command指令角色

需要執行的所有指令都在這裡聲明。

  • Invoker調用者角色

接收到指令,并執行指令。在例子中,我(項目經理)就是這個角色。

使用場景:

認為是指令的地方就可以采用指令模式,例如,在GUI開發中,一個按鈕的點選是一個指令,可以采用指令模式;模拟DOS指令的時候,當然也要采用指令模式;觸發-回報機制的處理等。

責任鍊模式(Chain of Responsibility Pattern)

定義:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.(使多個對象都有機會處理請求,進而避免了請求的發送者和接受者之間的耦合關系。将這些對象連成一條鍊,并沿着這條鍊傳遞該請求,直到有對象處理它為止。)

責任鍊模式類圖:

23種設計模式彙總概述
  • Handler: 抽象處理者
  • ConcreteHandler: 具體處理者
  • Client: 客戶類

抽象處理者的代碼:

public abstract class Handler {
    private Handler nextHandler;

    //每個處理者都必須對請求做出處理
    public final Response handleMessage(Request request) {
        Response response = null;
        //判斷是否是自己的處理級别
        if (this.getHandlerLevel().equals(request.getRequestLevel())) {
            response = this.echo(request);
        } else { //不屬于自己的處理級别
            //判斷是否有下一個處理者
            if (this.nextHandler != null) {
                response = this.nextHandler.handleMessage(request);
            } else {
            //沒有适當的處理者,業務自行處理
            }
        }
        return response;
    }

    //設定下一個處理者是誰
    public void setNext(Handler _handler) {
        this.nextHandler = _handler;
    }

    //每個處理者都有一個處理級别
    protected abstract Level getHandlerLevel();

    //每個處理者都必須實作處理任務
    protected abstract Response echo(Request request);
}
           

抽象的處理者實作三個職責:

一是定義一個請求的處理方法handleMessage,唯一對外開放的方法;

二是定義一個鍊的編排方法setNext,設定下一個處理者;

三是定義了具體的請求者必須實作的兩個方法:定義自己能夠處理的級别getHandlerLevel和具體的處理任務echo。

💡注意事項:

鍊中節點數量需要控制,避免出現超長鍊的情況,一般的做法是在Handler中設定一個最大節點數量,在setNext方法中判斷是否已經是超過其門檻值,超過則不允許該鍊建立,避免無意識地破壞系統性能。