行爲型設計模式(模板/策略/命令/職責鏈/狀態/觀察者/中介者/迭代器/訪問者/備忘錄/解釋器)

前言

因爲設計模式種類多,且重理解重回憶,所以本文儘量言簡意賅,便於時時溫習。

設計模式(Design Pattern)是前輩們對代碼開發經驗的總結,是解決特定問題的一系列套路。它不是語法規定,而是一套用來提高代碼可複用性、可維護性、可讀性、穩健性以及安全性的解決方案。

1995年,GoF(Gang of Four,四人組/四人幫)合作出版了《設計模式:可複用面向對象軟件的基礎》一書,共收錄了23種設計模式,從此樹立了軟件設計模式領域的里程碑,人稱「GoF設計模式」。

這 23 種設計模式的本質是面向對象設計原則的實際運用,是對類的封裝性、繼承性和多態性,以及類的關聯關係和組合關係的充分理解。

當然,軟件設計模式只是一個引導,在實際的軟件開發中,必須根據具體的需求來選擇: 對於簡單的程序,可能寫一個簡單的算法要比引入某種設計模式更加容易; 但是對於大型項目開發或者框架設計,用設計模式來組織代碼顯然更好。

我們要清楚,設計模式並不是Java的專利,它同樣適用於C++、C#、JavaScript等其它面向對象的編程語言。

設計原則

開閉原則

開閉原則的含義是:當應用的需求改變時,在不修改軟件實體的源代碼或者二進制代碼的前提下,可以擴展模塊的功能,使其滿足新的需求。

里氏替換原則

里氏替換原則通俗來講就是:子類可以擴展父類的功能,但不能改變父類原有的功能。也就是說:子類繼承父類時,除添加新的方法完成新增功能外,儘量不要重寫父類的方法。

依賴倒置原則

依賴倒置原則的原始定義爲:高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象。其核心思想是:要面向接口編程,不要面向實現編程。

依賴倒置原則是實現開閉原則的重要途徑之一,它降低了客戶與實現模塊之間的耦合。

單一職責原則

單一職責原則又稱單一功能原則,由羅伯特·C.馬丁(Robert C. Martin)於《敏捷軟件開發:原則、模式和實踐》一書中提出的。這裏的職責是指類變化的原因,單一職責原則規定一個類應該有且僅有一個引起它變化的原因,否則類應該被拆分。

該原則提出對象不應該承擔太多職責,如果一個對象承擔了太多的職責,至少存在以下兩個缺點:

  1. 一個職責的變化可能會削弱或者抑制這個類實現其他職責的能力;
  2. 當客戶端需要該對象的某一個職責時,不得不將其他不需要的職責全都包含進來,從而造成冗餘代碼或代碼的浪費。

迪米特法則

迪米特法則的定義是:只與你的直接朋友交談,不跟“陌生人”說話。其含義是:如果兩個軟件實體無須直接通信,那麼就不應當發生直接的相互調用,可以通過第三方轉發該調用。其目的是降低類之間的耦合度,提高模塊的相對獨立性。

迪米特法則中的“朋友”是指:當前對象本身、當前對象的成員對象、當前對象所創建的對象、當前對象的方法參數等,這些對象同當前對象存在關聯、聚合或組合關係,可以直接訪問這些對象的方法。

模式分類

根據目的來分類

根據模式是用來完成什麼工作來劃分,這種方式可分爲創建型模式、結構型模式和行爲型模式3種。

創建型模式:用於描述“怎樣創建對象”,它的主要特點是“將對象的創建與使用分離”。 GoF中提供了單例、原型、工廠方法、抽象工廠、建造者等5種創建型模式。 結構型模式:用於描述如何將類或對象按某種佈局組成更大的結構。 GoF中提供了代理、適配器、橋接、裝飾、外觀、享元、組合等7種結構型模式。 行爲型模式:用於描述類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,以及怎樣分配職責。 GoF中提供了模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄、解釋器等11種行爲型模式。

根據作用範圍來分類

根據模式是主要用於類上還是主要用於對象上來分,這種方式可分爲類模式和對象模式兩種。 類模式:用於處理類與子類之間的關係,這些關係通過繼承來建立,是靜態的,在編譯時刻便確定下來了。 GoF中的工廠方法、(類)適配器、模板方法、解釋器屬於該模式。 對象模式:用於處理對象之間的關係,這些關係可以通過組合或聚合來實現,在運行時刻是可以變化的,更具動態性。 GoF中除了以上4種,其他的都是對象模式。

創建型模式 結構型模式 行爲型模式
類模式 工廠方法 (類)適配器 模板方法、解釋器
對象模式 單例、原型、抽象工廠、建造者 代理、(對象)適配器、橋接、裝飾、外觀、享元、組合 策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄

1 策略模式

【介紹】

策略模式(Strategy Pattern)該模式定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。它通過對算法進行封裝,把使用算法的責任和算法的實現分割開來,並委派給不同的對象對這些算法進行管理。

策略模式允許我們在實現某一個功能時,如果存在多種算法或者策略,我們可以根據環境或者條件的不同選擇不同的算法或者策略來完成該功能,如數據排序策略有冒泡排序、選擇排序、插入排序、二叉樹排序等,我們可以根據不同的場景使用不同的算法。

如果使用多重條件轉移語句實現(即硬編碼,if-else),不但使條件語句變得很複雜,而且增加、刪除或更換算法要修改原代碼,不易維護,違背開閉原則。如果採用策略模式就能很好解決該問題。

【比喻】

在現實生活中常常遇到實現某種目標存在多種策略可供選擇的情況,例如,出行旅遊可以乘坐飛機、乘坐火車、騎自行車或自己開私家車等,超市促銷可以釆用打折、送商品、送積分等方法。

【優點】

  1. 多重條件語句不易維護,而使用策略模式可以避免使用多重條件語句,如 if...else 語句、switch...case 語句。
  2. 策略模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類裏面,從而避免重複的代碼。
  3. 策略模式可以提供相同行爲的不同實現,客戶可以根據不同時間或空間要求選擇不同的。
  4. 策略模式提供了對開閉原則的完美支持,可以在不修改原代碼的情況下,靈活增加新算法。
  5. 策略模式把算法的使用放到環境類中,而算法的實現移到具體策略類中,實現了二者的分離。

【缺點】

  1. 客戶端必須理解所有策略算法的區別,以便適時選擇恰當的算法類。
  2. 策略模式造成很多的策略類,增加維護難度。

【應用】

Spring在具體實例化Bean的過程中,創建對象時先通過ConstructorResolver找到對應的實例化方法和參數,再通過實例化策略InstantiationStrategy進行實例化,它有兩種具體策略類,分別爲SimpleInstantiationStrategy和CglibSubclassingInstantiationStrategy,前者對構造方法無MethodOverrides的對象使用反射來構造對象,而構造方法有MethodOverrides的對象則交給CglibSubclassingInstantiationStrategy來創建。

【案例】

//抽象策略類
interface Strategy {
    public void strategyMethod();    //策略方法
}
//具體策略類A
class ConcreteStrategyA implements Strategy {
    public void strategyMethod() {
        System.out.println("具體策略A的策略方法被訪問!");
    }
}
//具體策略類B
class ConcreteStrategyB implements Strategy {
    public void strategyMethod() {
        System.out.println("具體策略B的策略方法被訪問!");
    }
}
//環境類
class Context {
    private Strategy strategy;
    public Strategy getStrategy() {
        return strategy;
    }
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    public void strategyMethod() {
        strategy.strategyMethod();
    }
}
public class StrategyPattern {
    public static void main(String[] args) {
        Context c = new Context();
        Strategy s = new ConcreteStrategyA();
        c.setStrategy(s);
        c.strategyMethod();
        System.out.println("-----------------");
        s = new ConcreteStrategyB();
        c.setStrategy(s);
        c.strategyMethod();
    }
}
具體策略A的策略方法被訪問!
-----------------
具體策略B的策略方法被訪問!

2 觀察者模式

【介紹】

觀察者模式(Observer Pattern)指多個對象間存在一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。這種模式有時又稱作發佈-訂閱模式、模型-視圖模式。

它的關鍵實現是在抽象類裏有一個列表存放觀察者們。一旦有變動發生,則依次調用這些觀察者的相關方法。

【比喻】

就是現實中的發佈-訂閱模型,或者說廣播模型。

【優點】

  1. 降低了目標與觀察者之間的耦合關係,兩者之間是抽象耦合關係。符合依賴倒置原則。
  2. 目標與觀察者之間建立了一套觸發機制。

【缺點】

  1. 目標與觀察者之間的依賴關係並沒有完全解除,而且有可能出現循環引用。
  2. 當觀察者對象很多時,通知的發佈會花費很多時間,影響程序的效率。

【應用】

Spring中的監聽機制就使用到了觀察者模式,其中:

  1. 觀察者們需要實現ApplicationListener<E extends ApplicationEvent>接口,這是抽象觀察者。
  2. 抽象目標(或者叫做抽象的消息發佈者)是ApplicationEventPublisherAware接口
  3. Spring觀察者模式發佈事件的代碼都在ApplicationEventPublisher類中,所以我們生成的具體目標(或者叫做具體的消息發佈者)沒必要自己編寫代碼,直接調用ApplicationEventPublisherpublishEvent方法即可。
  4. Spring中的事件要繼承ApplicationEvent類,即觀察者模式中的主題,可以看做一個普通的bean類,用於保存在事件監聽器的業務邏輯中需要的一些字段;

發佈事件之後,在Spring的ApplicationEventPublisher的底層,SimpleApplicationEventMulticater從容器中獲取所有的監聽器列表,遍歷列表,對每個監聽器分別執行invokeListener方法,緊接着它會調用一個doInvokeListener方法,該方法就會調用ApplicationListeneronApplicationEvent方法。

【案例】

//抽象目標,也是抽象的消息的發佈者
abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();
    //增加觀察者的方法
    public void add(Observer observer) {
        observers.add(observer);
    }
    //刪除觀察者的方法
    public void remove(Observer observer) {
        observers.remove(observer);
    }
    public abstract void notifyObserver(); //通知觀察者方法
}
//具體目標,也是具體的消息的發佈者
class ConcreteSubject extends Subject {
    public void notifyObserver() {
        System.out.println("具體目標發生改變...");
        System.out.println("--------------");
        for (Object obs : observers) {
            ((Observer) obs).response();
        }
    }
}
//抽象觀察者
interface Observer {
    void response(); //反應
}
//具體觀察者1
class ConcreteObserver1 implements Observer {
    public void response() {
        System.out.println("具體觀察者1作出反應!");
    }
}
//具體觀察者1
class ConcreteObserver2 implements Observer {
    public void response() {
        System.out.println("具體觀察者2作出反應!");
    }
}

public class ObserverPattern {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        Observer obs1 = new ConcreteObserver1();
        Observer obs2 = new ConcreteObserver2();
        subject.add(obs1);
        subject.add(obs2);
        subject.notifyObserver();
    }
}
具體目標發生改變...
--------------
具體觀察者1作出反應!
具體觀察者2作出反應!

3 責任鏈模式

【介紹】

責任鏈(Chain of Responsibility)模式,是爲了避免請求發送者與多個請求接收者耦合在一起,於是將所有請求的接收者通過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生時,可將請求沿着這條鏈傳遞,直到有對象處理它爲止。

在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。

在責任鏈模式中,客戶只需要將請求發送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,請求會自動進行傳遞。所以責任鏈將請求的發送者和請求的處理者解耦了。

通常情況下,可以通過數據鏈表來實現職責鏈模式的數據結構。

【比喻】

在現實生活中,一個事件需要經過多個對象處理是很常見的場景。例如,公司員工請假,可批假的領導有部門負責人、副總經理、總經理等,但每個領導能批准的天數不同,員工必須根據需要請假的天數去找不同的領導簽名,也就是說員工必須記住每個領導的姓名、電話和地址等信息。

【優點】

  1. 降低了對象之間的耦合度。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鏈的結構,發送者和接收者也無須擁有對方的明確信息。
  2. 增強了系統的可擴展性。可以根據需要增加新的請求處理類,滿足開閉原則。
  3. 增強了給對象指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。
  4. 責任鏈簡化了對象之間的連接。每個對象只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用衆多的 if 或者 if···else 語句。
  5. 責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明確各類的責任範圍,符合類的單一職責原則。

【缺點】

  1. 不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。
  2. 對比較長的責任鏈,請求的處理可能涉及多個處理對象,系統性能將受到一定影響。
  3. 責任鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設置而導致系統出錯,如可能會造成循環調用。

【應用】

  1. Apache Tomcat對Encoding的處理
  2. Struts2的攔截器
  3. jsp servlet的Filter。
  4. Spring中的過濾器ApplicationFilterChain。

【案例】

//抽象處理者角色
abstract class Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public Handler getNext() {
        return next;
    }

    //處理請求的方法
    public abstract void handleRequest(String request);
}

//具體處理者角色1
class ConcreteHandler1 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("one")) {
            System.out.println("具體處理者1負責處理該請求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("沒有人處理該請求!");
            }
        }
    }
}

//具體處理者角色2
class ConcreteHandler2 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("two")) {
            System.out.println("具體處理者2負責處理該請求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("沒有人處理該請求!");
            }
        }
    }
}

public class ChainOfResponsibilityPattern {
    public static void main(String[] args) {
        //組裝責任鏈
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.setNext(handler2);
        //提交請求
        handler1.handleRequest("two");
    }
}

4 模板模式

【介紹】

模板模式(Template Pattern)定義了一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行爲型模式。

【比喻】

例如,去銀行辦理業務一般要經過以下4個流程:取號、排隊、辦理具體業務、對銀行工作人員進行評分等。

其中取號、排隊和對銀行工作人員進行評分的業務對每個客戶是一樣的,可以在父類中實現,但是辦理具體業務卻因人而異,它可能是存款、取款或者轉賬等,可以延遲到子類中實現。

這樣的例子在生活中還有很多,例如,一個人每天會起牀、喫飯、做事、睡覺等,其中“做事”的內容每天可能不同。我們把這些規定了流程或格式的實例定義成模板,允許使用者根據自己的需求去更新它,例如,簡歷模板、論文模板等。

【優點】

  1. 它封裝了不變部分,擴展可變部分。它把認爲是不變部分的算法封裝到父類中實現,而把可變部分算法由子類繼承實現,便於子類繼續擴展。
  2. 它在父類中提取了公共的部分代碼,便於代碼複用。
  3. 部分方法是由子類實現的,因此子類可以通過擴展方式增加相應的功能,符合開閉原則。

【缺點】

  1. 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象,間接地增加了系統實現的複雜度。
  2. 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。
  3. 由於繼承關係自身的缺點,如果父類添加新的抽象方法,則所有子類都要改一遍。

【應用】

  1. Java Servlet中,HttpServlet這個類就是一個抽象的模板類,它定義了doGet,doPost,doHead,doDelete等一系列的抽象方法,並在service方法中規定了前面這些方法的執行順序和條件,形成了http訪問的模板。我們定義的新的servlet子類,只需要繼承HttpServlet,並實現doGet,doPost等方法即可。
  2. Mybatis中,BaseExecutor定義了數據庫操作的基本模板:doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法。繼承BaseExecutor的子類只需要實現四個基本方法來完成數據庫的相關操作即可。
  3. SpringBoot爲用戶封裝了很多繼承代碼,都用到了模板方式,例如那一堆XXXtemplate。

【案例】

//抽象類
abstract class AbstractClass {
    //模板方法
    public void TemplateMethod() {
        SpecificMethod();
        abstractMethod1();
        abstractMethod2();
    }

    //具體方法
    public void SpecificMethod() {
        System.out.println("抽象類中的具體方法被調用...");
    }

    //抽象方法1
    public abstract void abstractMethod1();

    //抽象方法2
    public abstract void abstractMethod2();
}

//具體子類
class ConcreteClass extends AbstractClass {
    public void abstractMethod1() {
        System.out.println("抽象方法1的實現被調用...");
    }

    public void abstractMethod2() {
        System.out.println("抽象方法2的實現被調用...");
    }
}

public class TemplateMethodPattern {
    public static void main(String[] args) {
        AbstractClass tm = new ConcreteClass();
        tm.TemplateMethod();
    }
}
抽象類中的具體方法被調用...
抽象方法1的實現被調用...
抽象方法2的實現被調用...

5 狀態模式

【介紹】

狀態模式(State Pattern)對有狀態的對象,把複雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行爲。

在軟件開發過程中,應用程序中的部分對象可能會根據不同的情況做出不同的行爲,我們把這種對象稱爲有狀態的對象,而把影響對象行爲的一個或多個動態變化的屬性稱爲狀態。

當有狀態的對象與外部事件產生互動時,其內部狀態就會發生改變,從而使其行爲也發生改變。

對這種有狀態的對象編程,傳統的解決方案是:將這些所有可能發生的情況全都考慮到,然後使用if-else或switch-case語句來做狀態判斷,再進行不同情況的處理。但是顯然這種做法對複雜的狀態判斷存在天然弊端,條件判斷語句會過於臃腫,可讀性差,且不具備擴展性,維護難度也大。

以上問題如果採用“狀態模式”就能很好地得到解決。狀態模式的解決思想是:當控制一個對象狀態轉換的條件表達式過於複雜時,把相關“判斷邏輯”提取出來,用各個不同的類進行表示,系統處於哪種情況,直接使用相應的狀態類對象進行處理,這樣能把原來複雜的邏輯判斷簡單化,消除了 if-else、switch-case 等冗餘語句,代碼更有層次性,並且具備良好的擴展力。

【比喻】

例如人都有高興和傷心的不同狀態,不同的狀態有不同的行爲,將不同的狀態及其對應的行爲封裝成獨立的狀態對象,這樣就可以根據情緒表現出不同的行爲,同時不同的行爲也會反饋自己切換成不同的狀態。

【優點】

  1. 結構清晰,狀態模式將與特定狀態相關的行爲局部化到一個狀態中,並且將不同狀態的行爲分割開來,滿足“單一職責原則”。
  2. 將狀態轉換顯示化,減少對象間的相互依賴。將不同的狀態引入獨立的對象中會使得狀態轉換變得更加明確,且減少對象間的相互依賴。
  3. 狀態類職責明確,有利於程序的擴展。通過定義新的子類很容易地增加新的狀態和轉換。

【缺點】

  1. 狀態模式的使用必然會增加系統的類與對象的個數。
  2. 狀態模式的結構與實現都較爲複雜,如果使用不當會導致程序結構和代碼的混亂。
  3. 狀態模式對開閉原則的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源碼,否則無法切換到新增狀態,而且修改某個狀態類的行爲也需要修改對應類的源碼。

【應用】: Spring中的狀態機stateMachine。

【案例】

//環境類
class Context {
    private State state;

    //定義環境類的初始狀態
    public Context() {
        this.state = new ConcreteStateA();
    }

    //設置新狀態
    public void setState(State state) {
        this.state = state;
    }

    //讀取狀態
    public State getState() {
        return (state);
    }

    //對請求做處理
    public void Handle() {
        state.Handle(this);
    }
}

//抽象狀態類
abstract class State {
    public abstract void Handle(Context context);
}

//具體狀態A類
class ConcreteStateA extends State {
    public void Handle(Context context) {
        System.out.println("當前狀態是 A.");
        context.setState(new ConcreteStateB());
    }
}

//具體狀態B類
class ConcreteStateB extends State {
    public void Handle(Context context) {
        System.out.println("當前狀態是 B.");
        context.setState(new ConcreteStateA());
    }
}

public class StatePatternClient {
    public static void main(String[] args) {
        Context context = new Context();    //創建環境      
        context.Handle();    //處理請求
        context.Handle();
        context.Handle();
        context.Handle();
    }
}

輸出

當前狀態是 A.
當前狀態是 B.
當前狀態是 A.
當前狀態是 B.

狀態模式和策略模式看起來很像,UML圖都很像,但其實含義不一樣。狀態模式重點在各狀態之間的切換從而做不同的事情,而策略模式更側重於根據具體情況選擇不同策略,並不涉及切換,策略之間是完全獨立的。同時,在狀態模式中,每個狀態通過持有Context的引用,來實現狀態轉移;但是每個策略都不持有Context的引用,它們只是被Context使用。


6 迭代器模式

【介紹】

迭代器(Iterator Pattern)模式提供一個對象來順序訪問集合對象中的一系列數據,它在客戶訪問類與集合類之間插入一個迭代器,這分離了集合對象與其遍歷行爲,對客戶也隱藏了其內部細節而不暴露集合對象的內部表示。

例如Java中的Collection、List、Set、Map等都包含了迭代器。在日常開發中,我們幾乎不會自己寫迭代器。除非需要定製一個自己實現的數據結構對應的迭代器,否則,開源框架提供的API完全夠用。

【比喻】

比如:物流系統中的傳送帶,不管傳送的是什麼物品,都會被打包成一個個箱子,並且有一個統一的二維碼。這樣我們不需要關心箱子裏是什麼,在分發時只需要一個個檢查發送的目的地即可。

比如,我們平時乘坐交通工具,上車的隊列,都是統一刷卡或者刷臉進站,而不需要關心是男性還是女性、是殘疾人還是正常人等信息。

【優點】

  1. 訪問一個集合對象的內容而無須暴露它的內部表示。
  2. 遍歷任務交由迭代器完成,這簡化了聚合類。
  3. 它支持以不同方式遍歷一個集合,甚至可以自定義迭代器的子類以支持新的遍歷。
  4. 增加新的集合類和迭代器類都很方便,無須修改原有代碼。
  5. 封裝性良好,爲遍歷不同的集合結構提供一個統一的接口。

【缺點】

增加了類的個數,這在一定程度上增加了系統的複雜性。

【應用】

Java中的Collection、List、Set、Map等都包含了迭代器。

【案例】

//抽象集合
interface Aggregate {
    public void add(Object obj);

    public void remove(Object obj);

    public Iterator getIterator();
}

//具體集合
class ConcreteAggregate implements Aggregate {
    private List<Object> list = new ArrayList<Object>();

    public void add(Object obj) {
        list.add(obj);
    }

    public void remove(Object obj) {
        list.remove(obj);
    }

    public Iterator getIterator() {
        return (new ConcreteIterator(list));
    }
}

//抽象迭代器
interface Iterator {
    Object first();

    Object next();

    boolean hasNext();
}

//具體迭代器
class ConcreteIterator implements Iterator {
    private List<Object> list = null;
    private int index = -1;

    public ConcreteIterator(List<Object> list) {
        this.list = list;
    }

    public boolean hasNext() {
        if (index < list.size() - 1) {
            return true;
        } else {
            return false;
        }
    }

    public Object first() {
        index = 0;
        Object obj = list.get(index);
        ;
        return obj;
    }

    public Object next() {
        Object obj = null;
        if (this.hasNext()) {
            obj = list.get(++index);
        }
        return obj;
    }
}

public class IteratorPattern {
    public static void main(String[] args) {
        Aggregate ag = new ConcreteAggregate();
        ag.add("中山大學");
        ag.add("華南理工");
        ag.add("韶關學院");
        System.out.print("聚合的內容有:");
        Iterator it = ag.getIterator();
        while (it.hasNext()) {
            Object ob = it.next();
            System.out.print(ob.toString() + "\t");
        }
        Object ob = it.first();
        System.out.println("\nFirst:" + ob.toString());
    }
}
聚合的內容有:中山大學    華南理工    韶關學院   
First:中山大學

7 命令模式

【介紹】

命令(Command Pattern)模式將一個請求封裝爲一個對象,使發出請求的責任和執行請求的責任分割開。這樣兩者之間通過命令對象進行溝通,這樣方便將命令對象進行儲存、傳遞、調用、增加與管理。

在命令對象內部持有處理該命令的接受者,這樣每個命令和其接受者的關係就得到了綁定。

通過把命令封裝爲一個對象,命令發送者把命令對象發出後,就不去管是誰來接受處理這個命令,命令接受者接受到命令對象後進行處理,也不用管命令是誰發出的,所以命令模式實現了發送者與接受者之間的解耦,而具體把命令發送給誰還需要一個控制器。

【比喻】

在現實生活中,命令模式的例子也很多。比如看電視時,我們只需要輕輕一按遙控器就能完成頻道的切換,這就是命令模式,將換臺請求和換臺處理完全解耦了。電視機遙控器(命令發送者)通過按鈕(具體命令)來遙控電視機(命令接收者)。而對於電視機遙控器來說,它只能操控電視,它操控的對象已經和遙控器綁定了,我們不管裏面的邏輯。

對於用戶來說,我們想看電視,就只管找電視遙控器,不關心電視遙控器是如何打開電視的,想開空調,就只管找空調遙控器,以此類推。

同樣的,電視作爲接受者,也不關心是誰打開了它,它只和遙控器綁定,如果哪天電視要升級改版,也和發送者沒關係。

【優點】

  1. 通過引入中間件(抽象接口)降低系統的耦合度。
  2. 擴展性良好,增加或刪除命令非常方便。採用命令模式增加與刪除命令不會影響其他類,且滿足“開閉原則”。
  3. 可以實現宏命令。命令模式可以與組合模式結合,將多個命令裝配成一個組合命令,即宏命令。
  4. 方便實現Undo和Redo操作。命令模式可以與後面介紹的備忘錄模式結合,實現命令的撤銷與恢復。
  5. 可以在現有命令的基礎上,增加額外功能。比如日誌記錄,結合裝飾器模式會更加靈活。

【缺點】

  1. 可能產生大量具體的命令類。因爲每一個具體操作都需要設計一個具體命令類,這會增加系統的複雜性。
  2. 命令模式的結果其實就是接收方的執行結果,但是爲了以命令的形式進行架構、解耦請求與實現,引入了額外類型結構(引入了請求方與抽象命令接口),增加了理解上的困難。不過這也是設計模式的通病,抽象必然會額外增加類的數量,代碼抽離肯定比代碼聚合更加難理解。

【應用】

Tomcat作爲一個服務器本身會接受外部大量請求,當一個請求過來後tomcat根據域名去找對應的host,找到host後會根據應用名去找具體的context(應用),然後具體應用處理請求。

Tomcat中的Connector作爲命令發出者,Connector接受到請求後把請求內容封裝爲request對象(命令對象),然後使用CoyoteAdapter作爲分發器把請求具體發配到具體的host,host再根據request對象找到具體的context,至此找到了具體的應用,交給具體應用處理。

這就實現了:對於具體host來說他不關心這個請求是誰給的,對於Connector來說他也不必關心誰來處理,但是兩者是通過request封裝請求對象進行關聯起來。

【案例】

//調用者
class Invoker {
    private Command command;

    public Invoker(Command command) {
        this.command = command;
    }

    public void setCommand(Command command) {
        this.command = command;
    }

    public void call() {
        System.out.println("調用者執行命令command...");
        command.execute();
    }
}

//抽象命令
interface Command {
    public abstract void execute();
}

//具體命令
class ConcreteCommand implements Command {
    private Receiver receiver;

    ConcreteCommand(Receiver) {
        receiver = new Receiver();
    }

    public void execute() {
        receiver.action();
    }
}

//接收者
class Receiver {
    public void action() {
        System.out.println("接收者的action()方法被調用...");
    }
}

public class CommandPattern {
    public static void main(String[] args) {
        Command cmd = new ConcreteCommand();
        Invoker ir = new Invoker(cmd);
        System.out.println("客戶訪問調用者的call()方法...");
        ir.call();
    }
}

8 備忘錄模式

【介紹】

備忘錄(Memento Pattern)模式在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便以後當需要時能將該對象恢復到原先保存的狀態。該模式又叫快照模式。

其實很多應用軟件都提供了這項功能,如Word、記事本、Photoshop、Eclipse等軟件在編輯時按Ctrl+Z組合鍵時能撤銷當前操作,使文檔恢復到之前的狀態;

備忘錄模式能記錄一個對象的內部狀態,當用戶後悔時能撤銷當前操作,使數據恢復到它原先的狀態。

【比喻】

每個人都有犯錯誤的時候,都希望有種“後悔藥”能彌補自己的過失,讓自己重新開始,但現實是殘酷的。在計算機應用中,客戶同樣會常常犯錯誤,能否提供“後悔藥”給他們呢?當然是可以的,而且是有必要的。這個功能由備忘錄模式來實現。

【優點】

  1. 提供了一種可以恢復狀態的機制。當用戶需要時能夠比較方便地將數據恢復到某個歷史的狀態。
  2. 實現了內部狀態的封裝。除了創建它的發起人之外,其他對象都不能夠訪問這些狀態信息。
  3. 簡化了發起人類。發起人不需要管理和保存其內部狀態的各個備份,所有狀態信息都保存在備忘錄中,並由管理者進行管理,這符合單一職責原則。

【缺點】

資源消耗大。如果要保存的內部狀態信息過多或者特別頻繁,將會佔用比較大的內存資源。

【應用】

spring-webflow中的StateManageableMessageContext類,就才用了備忘錄模式,它接口中定義了createMessagesMemento()方法,其實現類DefaultMessageContext有其默認實現:

private Map<Object, List<Message>> sourceMessages;
public Serializable createMessagesMemento() {
    return new LinkedHashMap(this.sourceMessages);
}

【案例】

備忘錄模式使用三個類 Memento、Originator和CareTaker。

Memento用來存儲要被恢復的對象的狀態。Originator創建並在Memento對象中存儲狀態。Caretaker對象是Memento的管理者,負責管理存儲多版本的Memento,以及從Memento中恢復對象的狀態。

public class Memento {
   private String state;
 
   public Memento(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }  
}

public class Originator {
   private String state;
 
   public void setState(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }
 
   public Memento saveStateToMemento(){
      return new Memento(state);
   }
 
   public void getStateFromMemento(Memento Memento){
      state = Memento.getState();
   }
}

public class CareTaker {
   private List<Memento> mementoList = new ArrayList<Memento>();
 
   public void add(Memento state){
      mementoList.add(state);
   }
 
   public Memento get(int index){
      return mementoList.get(index);
   }
}

public class MementoPatternDemo {
   public static void main(String[] args) {
      Originator originator = new Originator();
      CareTaker careTaker = new CareTaker();
      originator.setState("State #1");
      originator.setState("State #2");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #3");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #4");
 
      System.out.println("Current State: " + originator.getState());    
      originator.getStateFromMemento(careTaker.get(0));
      System.out.println("First saved State: " + originator.getState());
      originator.getStateFromMemento(careTaker.get(1));
      System.out.println("Second saved State: " + originator.getState());
   }
}
Current State: State #4
First saved State: State #2
Second saved State: State #3

9 訪問者模式

【介紹】

訪問者模式(Visitor Pattern)將作用於集合類中的各元素的操作分離出來封裝成獨立的類,使其在不改變數據結構的前提下可以添加作用於這些元素的新的操作,爲數據結構中的每個元素提供多種訪問方式。

比較難理解?我們用商場的商品來比喻一下。

【比喻】

比如說在商場購物時放在購物車中的商品,購物車就是集合類,商品是元素(可能是不同類型),那麼我們知道,不同的訪問者,對於商品的操作是不一樣的。收銀員對商品的操作是計價,而顧客對商品的操作是使用。

常規情況下我們會在商品類中定義settle()方法用來計價,定義use()方法用來使用,但假如我們現在新增了一類訪問者呢?假如新增了一類質檢員,對商品進行質檢,難道我們還要將每個商品類都新增check()方法嗎?後面如果再來一類訪問者呢?

訪問者模式就是爲了解決這種痛點應運而生的。

【優點】

  1. 擴展性好。能夠在不修改對象結構中的元素的情況下,爲對象結構中的元素添加新的功能。
  2. 複用性好。可以通過訪問者來定義整個對象結構通用的功能,從而提高系統的複用程度。
  3. 靈活性好。訪問者模式將數據結構與作用於結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統的數據結構。
  4. 符合單一職責原則。訪問者模式把相關的行爲封裝在一起,構成一個訪問者,使每一個訪問者的功能都比較單一。

【缺點】

  1. 增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”。
  2. 破壞封裝。訪問者模式中具體元素對訪問者公佈細節,這破壞了對象的封裝性。
  3. 違反了依賴倒置原則。訪問者模式依賴了具體類,而沒有依賴抽象類。

【應用】

Spring的BeanDefinitionVisitor類被設計用來訪問BeanDefinition對象。PropertyPlaceholderConfigurer類會遍歷得到的所有的BeanDefinition對象,依次調用visitor.visitBeanDefinition(bd)方法。不過目前Spring目前只有BeanDefinitionVisitor一個訪問者類,但代碼中已經保留了拓展性。

【案例】

訪問者模式包含以下主要角色。

  1. 抽象訪問者(Visitor)角色:定義一個訪問具體元素的接口,爲每個具體元素類對應一個訪問操作visit(),該操作中的參數類型標識了被訪問的具體元素。
  2. 具體訪問者(ConcreteVisitor)角色:實現抽象訪問者角色中聲明的各個訪問操作,確定訪問者訪問一個元素時該做什麼。
  3. 抽象元素(Element)角色:聲明一個包含接受操作accept()的接口,被接受的訪問者對象作爲accept()方法的參數。
  4. 具體元素(ConcreteElement)角色:實現抽象元素角色提供的accept()操作,其方法體通常都是visitor.visit(this) ,另外具體元素中可能還包含本身業務邏輯的相關操作。
  5. 對象結構(Object Structure)角色:是一個包含元素角色的容器,提供讓訪問者對象遍歷容器中的所有元素的方法,通常由List、Set、Map等聚合類實現。

//抽象訪問者
interface Visitor {
    void visit(ConcreteElementA element);

    void visit(ConcreteElementB element);
}

//具體訪問者A類
class ConcreteVisitorA implements Visitor {
    public void visit(ConcreteElementA element) {
        System.out.println("具體訪問者A訪問,我是質檢員,進行質檢,元素A是罐頭商品,打開罐頭檢查---->" + element.operationA());
    }

    public void visit(ConcreteElementB element) {
        System.out.println("具體訪問者A訪問,我是質檢員,進行質檢,元素B是包裝袋商品,打開包裝檢查---->" + element.operationB());
    }
}

//具體訪問者B類
class ConcreteVisitorB implements Visitor {
    public void visit(ConcreteElementA element) {
        System.out.println("具體訪問者B訪問,我是消費者,元素A是罐頭商品,打開罐頭食用---->" + element.operationA());
    }

    public void visit(ConcreteElementB element) {
        System.out.println("具體訪問者B訪問,我是消費者,元素B是包裝袋商品,打開包裝食用---->" + element.operationB());
    }
}

//抽象元素類
interface Element {
    void accept(Visitor visitor);
}

//具體元素A類
class ConcreteElementA implements Element {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operationA() {
        return "具體元素A的操作。我是罐頭商品,打開罐頭。";
    }
}

//具體元素B類
class ConcreteElementB implements Element {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public String operationB() {
        return "具體元素B的操作。我是包裝袋商品,打開包裝袋。";
    }
}

//對象結構角色
class ObjectStructure {
    private List<Element> list = new ArrayList<Element>();

    public void accept(Visitor visitor) {
        Iterator<Element> i = list.iterator();
        while (i.hasNext()) {
            ((Element) i.next()).accept(visitor);
        }
    }

    public void add(Element element) {
        list.add(element);
    }

    public void remove(Element element) {
        list.remove(element);
    }
}

public class VisitorPattern {
    public static void main(String[] args) {
        ObjectStructure os = new ObjectStructure();
        os.add(new ConcreteElementA());
        os.add(new ConcreteElementB());
        Visitor visitor = new ConcreteVisitorA();
        os.accept(visitor);
        System.out.println("------------------------");
        visitor = new ConcreteVisitorB();
        os.accept(visitor);
    }
}
具體訪問者A訪問,我是質檢員,進行質檢,元素A是罐頭商品,打開罐頭檢查---->具體元素A的操作。我是罐頭商品,打開罐頭。
具體訪問者A訪問,我是質檢員,進行質檢,元素B是包裝袋商品,打開包裝檢查---->具體元素B的操作。我是包裝袋商品,打開包裝袋。
------------------------
具體訪問者B訪問,我是消費者,元素A是罐頭商品,打開罐頭食用---->具體元素A的操作。我是罐頭商品,打開罐頭。
具體訪問者B訪問,我是消費者,元素B是包裝袋商品,打開包裝食用---->具體元素B的操作。我是包裝袋商品,打開包裝袋。

10 中介者模式

【介紹】

中介者(Mediator Pattern)模式定義了一箇中介對象來封裝一系列對象之間的交互,使原有對象之間的耦合鬆散,且可以獨立地改變它們之間的交互。

在現實生活中,常常會出現好多對象之間存在複雜的交互關係,這種交互關係常常是“網狀結構”,它要求每個對象都必須知道它需要交互的對象。例如,每個人必須記住他(她)所有朋友的電話;而且,朋友中如果有人的電話修改了,他(她)必須讓其他所有的朋友一起修改,這叫作“牽一髮而動全身”,非常複雜。

如果把這種“網狀結構”改爲“星形結構”的話,將大大降低它們之間的“耦合性”,這時只要找一個“中介者”就可以了。如前面所說的“每個人必須記住所有朋友電話”的問題,只要在網上建立一個每個朋友都可以訪問的“通信錄”就解決了。

【比喻】

例如,你想租房,可以找房產中介,房產中介那裏有許多的房源信息。

例如,多個用戶可以向聊天室(中介類)發送消息,聊天室向所有的用戶顯示消息。

【優點】

  1. 類之間各司其職,符合迪米特法則。
  2. 降低了對象之間的耦合性,使得對象易於獨立地被複用。
  3. 將對象間的一對多關聯轉變爲一對一的關聯,提高系統的靈活性,使得系統易於維護和擴展。

【缺點】

中介者模式將原本多個對象直接的相互依賴變成了中介者和多個同事類的依賴關係。當同事類越多時,中介者就會越臃腫,變得複雜且難以維護。

【應用】

在各種的MVC框架中,其中C(控制器)就是M(模型)和V(視圖)的中介者。

【案例】

中介者模式包含以下主要角色。

  1. 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事對象註冊與轉發同事對象信息的抽象方法。
  2. 具體中介者(Concrete Mediator)角色:實現中介者接口,定義一個List來管理同事對象,協調各個同事角色之間的交互關係,因此它依賴於同事角色。
  3. 抽象同事類(Colleague)角色:定義同事類的接口,保存中介者對象,提供同事對象交互的抽象方法,實現所有相互影響的同事類的公共功能。
  4. 具體同事類(Concrete Colleague)角色:是抽象同事類的實現者,當需要與其他同事對象交互時,由中介者對象負責後續的交互。

//抽象中介者
abstract class Mediator {
    public abstract void register(Colleague colleague);

    public abstract void relay(Colleague cl); //轉發
}

//具體中介者
class ConcreteMediator extends Mediator {
    private List<Colleague> colleagues = new ArrayList<Colleague>();

    public void register(Colleague colleague) {
        if (!colleagues.contains(colleague)) {
            colleagues.add(colleague);
            colleague.setMedium(this);
        }
    }

    public void relay(Colleague cl) {
        for (Colleague ob : colleagues) {
            // 除了發出者,其他都要轉發消息
            if (!ob.equals(cl)) {
                ((Colleague) ob).receive();
            }
        }
    }
}

//抽象同事類
abstract class Colleague {
    protected Mediator mediator;

    public void setMedium(Mediator mediator) {
        this.mediator = mediator;
    }

    public abstract void receive();

    public abstract void send();
}

//具體同事類
class ConcreteColleague1 extends Colleague {
    public void receive() {
        System.out.println("具體同事類1收到請求。");
    }

    public void send() {
        System.out.println("具體同事類1發出請求。");
        mediator.relay(this); //請中介者轉發
    }
}

//具體同事類
class ConcreteColleague2 extends Colleague {
    public void receive() {
        System.out.println("具體同事類2收到請求。");
    }

    public void send() {
        System.out.println("具體同事類2發出請求。");
        mediator.relay(this); //請中介者轉發
    }
}

public class MediatorPattern {
    public static void main(String[] args) {
        Mediator md = new ConcreteMediator();
        Colleague c1, c2;
        c1 = new ConcreteColleague1();
        c2 = new ConcreteColleague2();
        md.register(c1);
        md.register(c2);
        c1.send();
        System.out.println("-------------");
        c2.send();
    }
}
具體同事類1發出請求。
具體同事類2收到請求。
-------------
具體同事類2發出請求。
具體同事類1收到請求。

11 解釋器模式

【介紹】

解釋器(Interpreter Pattern)模式給分析對象定義一個語言,並定義該語言的文法表示,再設計一個解析器來解釋語言中的句子。也就是說,用編譯語言的方式來分析應用中的實例。這種模式實現了文法表達式處理的接口,該接口解釋一個特定的上下文。

這種模式實現了一個表達式接口,該接口解釋一個特定的上下文。這種模式被用在SQL解析、符號處理引擎等。

在項目開發中,如果要對數據表達式進行分析與計算,無須再用解釋器模式進行設計了,Java提供了以下強大的數學公式解析器:Expression4J、MESP(Math Expression String Parser)和Jep等,它們可以解釋一些複雜的文法,功能強大,使用簡單。

【比喻】

【優點】

  1. 擴展性好。由於在解釋器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴展文法。
  2. 容易實現。在語法樹中的每個表達式節點類都是相似的,所以實現其文法較爲容易。

【缺點】

  1. 執行效率較低。解釋器模式中通常使用大量的循環和遞歸調用,當要解釋的句子較複雜時,其運行速度很慢,且代碼的調試過程也比較麻煩。
  2. 會引起類膨脹。解釋器模式中的每條規則至少需要定義一個類,當包含的文法規則很多時,類的個數將急劇增加,導致系統難以管理與維護。
  3. 可應用的場景比較少。在軟件開發中,需要定義語言文法的應用實例非常少,所以這種模式很少被使用到。

【應用】

用於SQL語句的解析。

【案例】

假如“韶粵通”公交車讀卡器可以判斷乘客的身份,如果是“韶關”或者“廣州”的“老人” “婦女”“兒童”就可以免費乘車,其他人員乘車一次扣2元。

然後,根據文法規則按以下步驟設計公交車卡的讀卡器程序的類圖。

  • 定義一個抽象表達式(Expression)接口,它包含了解釋方法interpret(String info)。
  • 定義一個終結符表達式(Terminal Expression)類,它用集合(Set)類來保存滿足條件的城市或人,並實現抽象表達式接口中的解釋方法 interpret(Stringinfo),用來判斷被分析的字符串是否是集合中的終結符。
  • 定義一個非終結符表達式(AndExpressicm)類,它也是抽象表達式的子類,它包含滿足條件的城市的終結符表達式對象和滿足條件的人員的終結符表達式對象,並實現 interpret(String info) 方法,用來判斷被分析的字符串是否是滿足條件的城市中的滿足條件的人員。
  • 最後,定義一個環境(Context)類,它包含解釋器需要的數據,完成對終結符表達式的初始化,並定義一個方法 freeRide(String info) 調用表達式對象的解釋方法來對被分析的字符串進行解釋。

//抽象表達式類
interface Expression {
    public boolean interpret(String info);
}

//終結符表達式類
class TerminalExpression implements Expression {
    private Set<String> set = new HashSet<String>();

    public TerminalExpression(String[] data) {
        for (int i = 0; i < data.length; i++) set.add(data[i]);
    }

    public boolean interpret(String info) {
        if (set.contains(info)) {
            return true;
        }
        return false;
    }
}

//非終結符表達式類
class AndExpression implements Expression {
    private Expression city = null;
    private Expression person = null;

    public AndExpression(Expression city, Expression person) {
        this.city = city;
        this.person = person;
    }

    public boolean interpret(String info) {
        String s[] = info.split("的");
        return city.interpret(s[0]) && person.interpret(s[1]);
    }
}

//環境類
class Context {
    private String[] citys = {"韶關", "廣州"};
    private String[] persons = {"老人", "婦女", "兒童"};
    private Expression cityPerson;

    public Context() {
        Expression city = new TerminalExpression(citys);
        Expression person = new TerminalExpression(persons);
        cityPerson = new AndExpression(city, person);
    }

    public void freeRide(String info) {
        boolean ok = cityPerson.interpret(info);
        if (ok) System.out.println("您是" + info + ",您本次乘車免費!");
        else System.out.println(info + ",您不是免費人員,本次乘車扣費2元!");
    }
}
public class InterpreterPatternDemo {
    public static void main(String[] args) {
        Context bus = new Context();
        bus.freeRide("韶關的老人");
        bus.freeRide("韶關的年輕人");
        bus.freeRide("廣州的婦女");
        bus.freeRide("廣州的兒童");
        bus.freeRide("山東的兒童");
    }
}
您是韶關的老人,您本次乘車免費!
韶關的年輕人,您不是免費人員,本次乘車扣費2元!
您是廣州的婦女,您本次乘車免費!
您是廣州的兒童,您本次乘車免費!
山東的兒童,您不是免費人員,本次乘車扣費2元!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章