天天看點

Java 版設計模式代碼案例 (三):行為型設計模式

作者:JAVA互聯搬磚勞工

1. 政策模式(Strategy)

政策模式定義了一系列算法,并将每個算法封裝起來,使他們可以互相替換,且算法的變化不會影響到使用算法的客戶。
  • 主要解決:在有多種算法相似的情況下,使用 if...else 所帶來的複雜和難以維護。
  • 如何解決:将這些算法封裝成一個一個的類,任意地替換。
  • 何時使用:一個系統有許多許多類,而區分它們的隻是他們直接的行為。

舉例實作一個加減的功能:

定義抽象政策角色:這個是一個抽象的角色,通常情況下使用接口或者抽象類去實作。

java複制代碼public interface Strategy {  
  
    int calc(int num1, int num2);  
  
}
           

定義具體政策角色:包裝了具體的算法和行為。就是實作了 Strategy 接口的實作一組實作類。

java複制代碼public class AddStrategy implements Strategy {  
  
    @Override  
    public int calc(int num1, int num2) {  
        return num1 + num2;  
    }  
  
}
           
java複制代碼public class SubtractStrategy implements Strategy {  
  
    @Override  
    public int calc(int num1, int num2) {  
        return num1 - num2;  
    }  
  
}
           

定義環境角色: 内部會持有一個抽象角色的引用,給用戶端調用。

JAVA複制代碼public enum CalcEnum {  
  
    ADD(AddStrategy.class),  
    SUBTRACT(SubtractStrategy.class);  

    private Class<? extends Strategy> clazz;  

    CalcEnum(Class<? extends Strategy> clazz) {  
        this.clazz = clazz;  
    }  

    public Class<?> getClazz() {  
        return clazz;  
    }  
  
}
           
java複制代碼public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        Strategy strategy1 = (Strategy) CalcEnum.ADD.getClazz().newInstance();  
        int result1 = strategy1.calc(1, 2);  
        System.out.println("1 + 2 = " + result1);  

        Strategy strategy2 = (Strategy) CalcEnum.SUBTRACT.getClazz().newInstance();  
        int result2 = strategy2.calc(5, 3);  
        System.out.println("5 - 3 = " + result2);  
    }  
  
}
           

運作後輸出結果:

java複制代碼1 + 2 = 3
5 - 3 = 2
           

政策模式的優點:1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴充性良好。

政策模式的缺點:1、政策類會增多。 2、所有政策類都需要對外暴露。

2. 模版模式(Template)

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

完成一件事情,有固定的數個步驟,但是每個步驟根據對象的不同,而實作細節不同;就可以在父類中定義一個完成該事情的總方法,按照完成事件需要的步驟去調用其每個步驟的實作方法。每個步驟的具體實作,由子類完成。

模版模式涉及的角色:

  • 抽象父類 (AbstractClass):實作了模闆方法,定義了算法的骨架。
  • 具體實作類 (ConcreteClass):實作抽象類中的抽象方法,即不同的對象的具體實作細節。

舉例一個下廚的抽象模版類:

java複制代碼public abstract class CookingTemplate {  
  
    protected void doCooking() {  
        this.preHandle();  
        this.doSomething();  
        this.postHandle();  
    }  

    public abstract void preHandle();  

    public abstract void doSomething();  

    public abstract void postHandle();  
  
}
           

宮爆雞丁 和 紅燒排骨 具體實作了菜該怎麼做:

java複制代碼public class KungPaoChicken extends CookingTemplate {  
  
    @Override  
    public void preHandle() {  
        System.out.println("宮爆雞丁 做菜前...");  
    }  

    @Override  
    public void doSomething() {  
        System.out.println("宮爆雞丁 做菜中...");  
    }  

    @Override  
    public void postHandle() {  
        System.out.println("宮爆雞丁 做菜後...");  
    }  
  
}
           
java複制代碼public class StewedSpareRibs extends CookingTemplate {  
  
    @Override  
    public void preHandle() {  
        System.out.println("紅燒排骨 做菜前...");  
    }  

    @Override  
    public void doSomething() {  
        System.out.println("紅燒排骨 做菜中...");  
    }  

    @Override  
    public void postHandle() {  
        System.out.println("紅燒排骨 做菜後...");  
    }  
  
}
           

運作後輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) {  
        KungPaoChicken kungPaoChicken = new KungPaoChicken();  
        kungPaoChicken.doCooking();  

        System.out.println("-----------------------------");  

        StewedSpareRibs stewedSpareRibs = new StewedSpareRibs();  
        stewedSpareRibs.doCooking();  
    }  
  
}
           
java複制代碼宮爆雞丁 做菜前...
宮爆雞丁 做菜中...
宮爆雞丁 做菜後...
-----------------------------
紅燒排骨 做菜前...
紅燒排骨 做菜中...
紅燒排骨 做菜後...
           

模闆模式的優點:

  1. 具體細節步驟實作定義在子類中,子類定義詳細處理算法是不會改變算法整體結構。
  2. 代碼複用的基本技術,在資料庫設計中尤為重要。
  3. 存在一種反向的控制結構,通過一個父類調用其子類的操作,通過子類對父類進行擴充增加新的行為,符合“開閉原則”。

模闆模式的缺點:

  1. 每個不同的實作都需要定義一個子類,會導緻類的個數增加,系統更加龐大。

3. 觀察者模式(Observer)

定義對象間的一種一對多的依賴關系,當一個對象的狀态發生改變時,所有依賴于它的對象都得到通知并被自動更新。
  • 主要解決:一個對象狀态改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協作。
  • 如何解決:使用面向對象技術,可以将這種依賴關系弱化。
  • 何時使用:一個對象(目标對象)的狀态發生改變,所有的依賴對象(觀察者對象)都将得到通知,進行廣播通知。

舉例一個微信公衆号服務,不定時釋出一些消息,關注公衆号就可以收到推送消息,取消關注就收不到推送消息:

定義一個抽象觀察者角色:為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。

java複制代碼public interface Observer {  
  
    void update(String message);  
  
}
           

定義一個抽象被觀察者接口:也就是一個抽象主題,它把所有對觀察者對象的引用儲存在一個集合中,每個主題都可以有任意數量的觀察者。抽象主題提供一個接口,可以增加和删除觀察者角色。一般用一個抽象類和接口來實作。

java複制代碼public interface Subject {  
  
    void registerObserver(Observer o);  

    void removeObserver(Observer o);  

    void notifyObserver();  
  
}
           

定義一個具體被觀察者角色:也就是一個具體的主題,在集體主題的内部狀态改變時,所有登記過的觀察者發出通知。

java複制代碼public class WechatServer implements Subject {  
  
    private List<Observer> list;  
    private String message;  

    public WechatServer() {  
        list = new ArrayList<Observer>();  
    }  

    @Override  
    public void registerObserver(Observer o) {  
        list.add(o);  
    }  

    @Override  
    public void removeObserver(Observer o) {  
        if (!list.isEmpty()) {  
            list.remove(o);  
        }  
    }  

    @Override  
    public void notifyObserver() {  
        for (Observer o : list) {  
            o.update(message);  
        }  
    }  

    public void sendInformation(String s) {  
        this.message = s;  
        System.out.println("微信服務發送消息: " + s);  
        // 消息更新,通知所有觀察者  
        notifyObserver();  
    }  
  
}
           

定義一個具體觀察者角色:實作抽象觀察者角色所需要的更新接口,使本身的狀态與制圖的狀态相協調。

java複制代碼public class WechatUser implements Observer {  
  
    private String name;  
    private String message;  

    public WechatUser(String name) {  
        this.name = name;  
    }  

    @Override  
    public void update(String message) {  
        this.message = message;  
        read();  
    }  

    public void read() {  
        System.out.println(name + " 收到推送消息: " + message);  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) {  

        WechatServer server = new WechatServer();  

        Observer observer1 = new WechatUser("Han Mei");  
        Observer observer2 = new WechatUser("Li Lei");  

        server.registerObserver(observer1);  
        server.registerObserver(observer2);  
        server.sendInformation("微信小程式擷取手機号即将收費!");  

        System.out.println("----------------------------------------------");  
        server.removeObserver(observer2);  
        server.sendInformation("JAVA 是世界上最好用的語言!");  

    }  
  
}
           
java複制代碼微信服務發送消息: 微信小程式擷取手機号即将收費!
Han Mei 收到推送消息: 微信小程式擷取手機号即将收費!
Li Lei 收到推送消息: 微信小程式擷取手機号即将收費!
----------------------------------------------
微信服務發送消息: JAVA 是世界上最好用的語言!
Han Mei 收到推送消息: JAVA 是世界上最好用的語言!
           

觀察者模式的優點:

  1. 建立一套觸發機制,使觀察者和被觀察者是抽象關聯的。

觀察者模式的缺點:

  1. 如果一個被觀察者對象有很多的直接和間接的觀察者的話,将所有的觀察者都通知到會花費很多時間。
  2. 如果在觀察者和觀察目标之間有循環依賴的話,觀察目标會觸發它們之間進行循環調用,可能導緻系統崩潰。
  3. 觀察者模式沒有相應的機制讓觀察者知道所觀察的目标對象是怎麼發生變化的,而僅僅隻是知道觀察目标發生了變化。

4. 疊代器模式(Iterator)

提供一種方法順序通路一個聚合對象中各個元素, 而又無須暴露該對象的内部表示。

簡單來說,不同種類的對象可能需要不同的周遊方式,我們對每一種類型的對象配一個疊代器,最後多個疊代器合成一個。

  • 主要解決:不同的方式來周遊整個整合對象。
  • 如何解決:把在元素之間遊走的責任交給疊代器,而不是聚合對象。
  • 何時使用:周遊一個聚合對象。

舉例我們建立一個疊代器接口:

java複制代碼public interface Iterator {  
  
    boolean hasNext();  

    Object next();  
  
}
           

建立一個店鋪的菜單,利用疊代器進行周遊:

java複制代碼public class MenuItem {  
  
    private final String name;  
    private final String description;  
    private final float price;  

    public MenuItem(String name, String description, float price) {  
        this.name = name;  
        this.description = description;  
        this.price = price;  
    }  

    public String getName() {  
        return name;  
    }  

    public String getDescription() {  
        return description;  
    }  

    public float getPrice() {  
        return price;  
    }  
  
}
           
java複制代碼public class MenuIterator implements Iterator {  
  
    private int position;  
    private final ArrayList<MenuItem> menuItems;  

    public MenuIterator(ArrayList<MenuItem> menuItems) {  
        position = 0;  
        this.menuItems = menuItems;  
    }  

    @Override  
    public boolean hasNext() {  
        return position < menuItems.size();  
    }  

    @Override  
    public Object next() {  
        MenuItem menuItem = menuItems.get(position);  
        position++;  
        return menuItem;  
    }  
  
}
           
java複制代碼public class ShopMenu {  
  
    private final ArrayList<MenuItem> menuItems;  

    public ShopMenu() {  
        menuItems = new ArrayList<>();  

        addItem("KFC Cake Breakfast", "boiled eggs & toast&cabbage", 3.99f);  
        addItem("MDL Cake Breakfast", "fried eggs & toast", 3.59f);  
        addItem("Strawberry Cake", "fresh strawberry", 3.29f);  
        addItem("Regular Cake Breakfast", "toast & sausage", 2.59f);  
    }  

    private void addItem(String name, String description, float price) {  
        MenuItem menuItem = new MenuItem(name, description, price);  
        menuItems.add(menuItem);  
    }  

    public ArrayList<MenuItem> getMenuItems() {  
        return menuItems;  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) {  
        ShopMenu shopMenu = new ShopMenu();  
        MenuIterator iterator = new MenuIterator(shopMenu.getMenuItems());  
        while (iterator.hasNext()) {  
            MenuItem menuItem = (MenuItem) iterator.next();  
            System.out.println(menuItem.getName() + " *** " + menuItem.getPrice() + "$ *** " + menuItem.getDescription());  
        }  
    }  
  
}
           
java複制代碼KFC Cake Breakfast *** 3.99$ *** boiled eggs & toast&cabbage
MDL Cake Breakfast *** 3.59$ *** fried eggs & toast
Strawberry Cake *** 3.29$ *** fresh strawberry
Regular Cake Breakfast *** 2.59$ *** toast & sausage
           

疊代器模式的優點:

  1. 它支援以不同的方式周遊一個聚合對象。
  2. 疊代器簡化了聚合類。
  3. 在同一個聚合上可以有多個周遊。
  4. 在疊代器模式中,增加新的聚合類和疊代器類都很友善,無須修改原有代碼。

疊代器模式的缺點:

  1. 由于疊代器模式将存儲資料和周遊資料的職責分離,增加新的聚合類需要對應增加新的疊代器類,類的個數成對增加,這在一定程度上增加了系統的複雜性。

5. 責任鍊模式(Chain)

如果有多個對象有機會處理請求,責任鍊可使請求的發送者和接受者解耦,請求沿着責任鍊傳遞,直到有一個對象處理了它為止。
  • 主要解決:責任鍊上的處理者負責處理請求,客戶隻需要将請求發送到責任鍊上即可,無須關心請求的處理細節和請求的傳遞,是以責任鍊将請求的發送者和請求的處理者解耦了。
  • 如何解決:攔截的類都實作統一接口。
  • 何時使用:在處理消息的時候以過濾很多道。

模拟 tomcat 的 filter 設計一個責任鍊:

java複制代碼public interface Filter {  

    void doFilter(FilterChain chain);  
  
}
           
java複制代碼public abstract class AbsFilter implements Filter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        chain.doFilter();  
    }  
  
}
           
java複制代碼public interface FilterChain {  
  
    void doFilter();  
  
}
           

我們來實作幾個具體的 Filter,并用責任鍊串起來:

java複制代碼public class FilterA extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("A過濾器開始工作");  
        super.doFilter(chain);  
    }  
  
}
           
java複制代碼public class FilterB extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("B過濾器開始工作");  
        super.doFilter(chain);  
    }  
  
}
           
java複制代碼public class FilterC extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("C過濾器開始工作");  
        super.doFilter(chain);  
    }  

}
           
java複制代碼public class FilterChainImpl implements FilterChain {  
  
    private List<Filter> filters = new ArrayList<>();  
    private int pos = 0;  

    public FilterChainImpl addFilter(Filter filter) {  
        filters.add(filter);  
        return this;  
    }  

    @Override  
    public void doFilter() {  
        if (pos < filters.size()) {  
        Filter filter = filters.get(pos++);  
        filter.doFilter(this);  
        }  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) {  
        FilterChain filterChain = new FilterChainImpl()  
            .addFilter(new FilterA())  
            .addFilter(new FilterB())  
            .addFilter(new FilterC());  
        filterChain.doFilter();  
    }  
  
}
           
java複制代碼A過濾器開始工作
B過濾器開始工作
C過濾器開始工作
           

如果我在鍊條中間中斷呢?下面我們來略微修改下 FilterB 的代碼:

java複制代碼public class FilterB extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("B過濾器停止工作");
        // super.doFilter(chain);  
    }  
  
}
           

是否執行後邊的 Filter,取決于目前過濾器處理完成後 chain 的處理方式。

java複制代碼A過濾器開始工作
B過濾器停止工作
           

責任鍊模式的優點:

降低了對象之間的耦合度增強了系統的可擴充性增強了給對象指派職責的靈活性責任鍊簡化了對象之間的連接配接責任分擔。

責任鍊模式的缺點:

對比較長的職責鍊,請求的處理可能涉及多個處理對象,系統性能将受到一定影響。職責鍊建立的合理性要靠用戶端來保證,增加了用戶端的複雜性,可能會由于職責鍊的錯誤設定而導緻系統出錯,如可能會造成循環調用。

6. 指令模式(Command)

将一個請求封裝為一個對象,使送出請求的責任和執行請求的責任分割開。這樣兩者之間通過指令對象進行溝通,這樣友善将指令對象進行儲存、傳遞、調用、增加與管理。

在某些場合,比如要對行為進行"記錄、撤銷/重做、事務"等處理,這種無法抵禦變化的緊耦合是不合适的。在這種情況下,如何将"行為請求者"與"行為實作者"解耦?将一組行為抽象為對象,可以實作二者之間的松耦合。

  • 主要解決:在軟體系統中,行為請求者與行為實作者通常是一種緊耦合的關系,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵禦變化的緊耦合的設計就不太合适。
  • 如何解決:通過調用者調用接受者執行指令,順序:調用者→接受者→指令。

舉例設計一個開關燈的指令模式代碼:

java複制代碼public class Light {  
  
    String loc = "";  

    public Light(String loc) {  
        this.loc = loc;  
    }  

    public void On() {  
        System.out.println(loc + " On");  
    }  

    public void Off() {  
        System.out.println(loc + " Off");  
    }  
  
}
           
java複制代碼public interface Command {  
  
    public void exec();  

    public void undo();  
  
}
           

我們來實作一個指令,然後設計一個控制器,來控制指令的執行:

java複制代碼public class TurnOnCommand implements Command {  
  
    private Light light;  

    public TurnOnCommand(Light light) {  
        this.light = light;  
    }  

    @Override  
    public void exec() {  
        light.On();  
    }  

    @Override  
    public void undo() {  
        light.Off();  
    }  
  
}
           
java複制代碼public class Controller {  
  
    public void execCommand(Command command) {  
        command.exec();  
    }  

    public void undoCommand(Command command) {  
        command.undo();  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) {  
        Light light = new Light("energy-saving light");  
        TurnOnCommand command = new TurnOnCommand(light);  
        command.exec();  
        command.undo();  
    }  
  
}
           
java複制代碼energy-saving light On
energy-saving light Off
           

7. 狀态模式(State)

在狀态模式中,我們建立表示各種狀态的對象和一個行為随着狀态對象改變而改變的 context 對象。簡單了解,一個擁有狀态的 context 對象,在不同的狀态下,其行為會發生改變。
  • 主要解決:對象的行為依賴于它的狀态(屬性),并且可以根據它的狀态改變而改變它的相關行為。
  • 如何解決:将各種具體的狀态類抽象出來。
  • 何時使用:代碼中包含大量與對象狀态有關的條件語句。

狀态模式一般和對象的狀态有關,實作類的方法有不同的功能,覆寫接口中的方法。狀态模式和指令模式一樣,也可以用于消除 if...else 等條件選擇語句。

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

java複制代碼public interface State {  
  
    void stateA2B(StateContext context);  

    void stateB2C(StateContext context);  

    void stateC2B(StateContext context);  
  
}
           
java複制代碼public class StateA implements State {  
  
    @Override  
    public void stateA2B(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态轉為: " + StateEnum.B.name());  
        context.setCurrent(StateEnum.B);  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态不比對,無法轉變! ");  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态不比對,無法轉變! ");  
    }  
  
}
           
java複制代碼public class StateB implements State {  
  
    @Override  
    public void stateA2B(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态不比對,無法轉變! ");  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态轉為: " + StateEnum.C.name());  
        context.setCurrent(StateEnum.C);  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态不比對,無法轉變! ");  
    }  
  
}
           
java複制代碼public class StateC implements State {  
  
    @Override  
    public void stateA2B(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态不比對,無法轉變! ");  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态不比對,無法轉變! ");  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        System.out.println("目前狀态: " + context.getCurrent().name() + " 狀态轉為: " + StateEnum.B.name());  
        context.setCurrent(StateEnum.B);  
    }  
  
}
           
java複制代碼public enum StateEnum {  
  
    A ("已付款", StateA.class),  
    B ("已發貨", StateB.class),  
    C ("已退貨", StateC.class);  

    private String desc;  
    private Class<? extends State> clazz;  

    StateEnum(String desc, Class<? extends State> clazz) {  
        this.desc = desc;  
        this.clazz = clazz;  
    }  

    public String getDesc() {  
        return desc;  
    }  

    public Class<? extends State> getClazz() {  
        return clazz;  
    }  
  
}
           

Context 環境角色:定義用戶端需要的接口,并且負責具體狀态的切換。

java複制代碼public class StateContext {  
  
    private StateEnum current;  

    public StateContext(StateEnum current) {  
        this.current = current;  
    }  

    public StateEnum getCurrent() {  
        return current;  
    }  

    public void setCurrent(StateEnum current) {  
        this.current = current;  
    }  
  
}
           
java複制代碼public class StateHandler implements State {  
  
    public static final Map<String, State> stateMap = new HashMap<>(StateEnum.values().length);  

    public static StateEnum initState() {  
        return StateEnum.A;  
    }  

    public StateHandler() throws Exception {  
        for (StateEnum stateEnum : StateEnum.values()) {  
            stateMap.put(stateEnum.name(), stateEnum.getClazz().newInstance());  
        }  
    }  

    @Override  
    public void stateA2B(StateContext context) {  
        State state = stateMap.get(context.getCurrent().name());  
        state.stateA2B(context);  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        State state = stateMap.get(context.getCurrent().name());  
        state.stateB2C(context);  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        State state = stateMap.get(context.getCurrent().name());  
        state.stateC2B(context);  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        StateContext context = new StateContext(StateHandler.initState());  
        StateHandler stateHandler = new StateHandler();  
        stateHandler.stateA2B(context);  
        stateHandler.stateA2B(context);  
        stateHandler.stateB2C(context);  
        stateHandler.stateC2B(context);  
    }  
  
}
           
java複制代碼目前狀态: A 狀态轉為: B
目前狀态: B 狀态不比對,無法轉變! 
目前狀态: B 狀态轉為: C
目前狀态: C 狀态轉為: B
           

狀态模式的優點:

  1. 封裝了轉換規則。
  2. 枚舉可能的狀态,在枚舉狀态之前需要确定狀态種類。
  3. 将所有與某個狀态有關的行為放到一個類中,并且可以友善地增加新的狀态,隻需要改變對象狀态即可改變對象的行為。
  4. 允許狀态轉換邏輯與狀态對象合成一體,而不是某一個巨大的條件語句塊。
  5. 可以讓多個環境對象共享一個狀态對象,進而減少系統中對象的個數。

狀态模式的缺點:

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

狀态模式 和 狀态機 的差別

狀态模式:一種程式設計模式。

狀态機:通常指 FSM (有限狀态機),是一個數學模型,是一種抽象機器,用狀态圖、狀态轉換圖) 表示。

數學領域中的狀态機:

  • 狀态機如同大多數數學模型一樣,并不是程式設計領域特有的名詞,而是為了解決某些問題而提出的數學模型。
  • 狀态機描述了在任何給定時間都處于某一個狀态的計算機,它可以響應某些輸入/觸發/事件而從一種狀态更改為另一種狀态,它的關注點是狀态及其轉換。
  • 許多工程和數學專業的學生已經了解了狀态機,但他們在程式設計領域幾乎沒有受過教育,在他們看來,狀态機是一個用"狀态圖"表示的數學模型、抽象機器。

程式設計領域中的狀态模式:

  • 狀态模式是程式設計領域特有的名詞,是一種設計模式。設計模式是為了更好地規範代碼設計結構,以便于封裝、複用、易于擴充;其中狀态模式用來解決 對象根據自己的狀态來展現出不同的行為。
  • 狀态模式是狀态機的一種實作方式,但通常都是以狀态模式的思路來實作狀态機。
  • 當你要用狀态模式實作一個功能的時候,這個功能結構肯定不适合被稱為"狀态模式",而更适合稱為"狀态機"。這隻是一個詞性的差別,就像"一碗米飯"要"用碗盛"一樣。
  • 然而狀态模式和狀态機的存在着很強的關聯性,就像"一碗米飯"必須"用碗盛"一樣,目前沒看到有其它好的實作方式。
  • 是以有些程式員認為狀态機是"運作中的狀态模式",這無可厚非。

8. 備忘錄模式(Memento)

在不破壞封裝性的前提下,捕獲一個對象的内部狀态,并在該對象之外儲存這個狀态,以便以後當需要時能将該對象恢複到原先儲存的狀态。該模式又叫快照模式。

備忘錄(Memento)角色:負責存儲發起人的内部狀态,在需要的時候提供這些内部狀态給發起人。

java複制代碼public class Memento {  

    private String state;  

    public Memento(String state) {  
        this.state = state;  
    }  

    public String getState() {  
        return state;  
    }  

    public void setState(String state) {  
        this.state = state;  
    }  
  
}
           

發起人(Originator)角色:記錄目前時刻的内部狀态資訊,提供建立備忘錄和恢複備忘錄資料的功能,實作其他業務功能,它可以通路備忘錄裡的所有資訊。

java複制代碼public class Originator {  
  
    private String state;  

    public Memento createMemento() {  
        return new Memento(state);  
    }  

    public void restoreMemento(Memento memento) {  
        this.state = memento.getState();  
    }  

    public String getState() {  
        return state;  
    }  

    public void setState(String state) {  
        this.state = state;  
    }  
  
}
           

管理者(Caretaker)角色:對備忘錄進行管理,提供儲存與擷取備忘錄的功能,但其不能對備忘錄的内容進行通路與修改。

java複制代碼public class Caretaker {  
  
    private List<Memento> mementoList = new ArrayList<>();  

    public void setMemento(Memento memento) {  
        mementoList.add(memento);  
    }  

    public Memento getMemento() {  
        Memento memento = null;  
        if (mementoList.size() > 0) {  
            memento = mementoList.get(mementoList.size() - 1);  
            // 恢複後移除這個狀态  
            mementoList.remove(mementoList.size() - 1);  
        }  
        return memento;  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  
  
    public static void main(String[] args) {  

        Caretaker caretaker = new Caretaker();  
        Originator originator = new Originator();  

        // 管理者使用備忘錄記錄狀态  
        originator.setState("狀态1");  
        caretaker.setMemento(originator.createMemento());  

        originator.setState("狀态2");  
        caretaker.setMemento(originator.createMemento());  

        // 修改原發器的狀态  
        originator.setState("狀态3");  
        System.out.println("目前合同狀态:" + originator.getState());  

        // 恢複備忘錄中存的狀态  
        originator.restoreMemento(caretaker.getMemento());  
        System.out.println("回退上一合同狀态:" + originator.getState());  

        originator.restoreMemento(caretaker.getMemento());  
        System.out.println("回退上一合同狀态:" + originator.getState());  
    }  
  
}
           
java複制代碼目前合同狀态:狀态3
回退上一合同狀态:狀态2
回退上一合同狀态:狀态1
           

9. 通路者模式(Visitor)

将作用于某種資料結構中的各元素的操作分離出來封裝成獨立的類,使其在不改變資料結構的前提下可以添加作用于這些元素的新的操作,為資料結構中的每個元素提供多種通路方式。它将對資料的操作與資料結構進行分離。

通路者模式 的基本想法是,軟體系統中擁有一個由許多對象構成的、比較穩定的對象結構,這些對象的類都擁有一個 accept 方法用來接受通路者對象的通路。通路者是一個接口,它擁有一個 visit 方法,這個方法對通路到的對象結構中不同類型的元素做出不同的處理。

舉例,對于元素資源紙張和金屬銅,不同的通路者公司利用,會使用不同的用途:

抽象元素(Element)角色:定義了一個接受通路者的方法(accept),其意義是指,每一個元素都要可以被通路者通路。

java複制代碼public interface Element {  

    String accept(Visitor visitor);  
  
}
           

具體元素(ConcreteElement)角色: 提供接受通路方法的具體實作,而這個具體的實作,通常情況下是使用通路者提供的通路該元素類的方法。

java複制代碼public class Paper implements Element {  
  
    @Override  
    public String accept(Visitor visitor) {  
        return visitor.create(this);  
    }  
  
}
           
java複制代碼public class Copper implements Element {  
  
    @Override  
    public String accept(Visitor visitor) {  
        return visitor.create(this);  
    }  
  
}
           

抽象通路者(Visitor)角色:定義了對每一個元素(Element)通路的行為,它的參數就是可以通路的元素,它的方法個數理論上來講與元素類個數(Element的實作類個數)是一樣的,從這點不難看出,通路者模式要求元素類的個數不能改變。

java複制代碼public interface Visitor {  
  
    String create(Paper element);  

    String create(Copper element);  
  
}
           

具體通路者(ConcreteVisitor)角色: 具體通路者實作了每個由抽象通路者聲明的操作,每一個操作用于通路對象結構中一種類型的元素。

java複制代碼public class ArtVisitor implements Visitor {  
  
    @Override  
    public String create(Paper element) {  
        return "列印廣告";  
    }  

    @Override  
    public String create(Copper element) {  
        return "制作銅像";  
    }  
  
}
           
java複制代碼public class MintVisitor implements Visitor {  
  
    @Override  
    public String create(Paper element) {  
        return "鑄造紙币";  
    }  

    @Override  
    public String create(Copper element) {  
        return "鑄造銅币";  
    }  
  
}
           

在對象結構的一次通路過程中,我們周遊整個對象結構,對每一個元素都實施 accept 方法,在每一個元素的 accept 方法中會調用通路者的 visit 方法,進而使通路者得以處理對象結構的每一個元素,我們可以針對對象結構設計不同的通路者類來完成不同的操作,達到差別對待的效果。

java複制代碼public class ElementSet {  
  
    private List<Element> list = new ArrayList<>();  

    public String accept(Visitor visitor) {  
        Iterator<Element> iterator = list.iterator();  
        StringBuilder result = new StringBuilder();  
        while (iterator.hasNext()) {  
            result.append(iterator.next().accept(visitor)).append(" ");  
        }  
        return result.toString();  
    }  

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

    public void remove(Element element) {  
        list.remove(element);  
    }  
  
}
           
java複制代碼public class MainTest {  
  
    public static void main(String[] args) {  
        ElementSet es = new ElementSet();  
        es.add(new Paper());  
        es.add(new Copper());  

        Visitor artVisitor = new ArtVisitor();  
        System.out.println(artVisitor.getClass().getSimpleName() + " " + es.accept(artVisitor));  

        System.out.println("==========================");  

        Visitor mintVisitor = new MintVisitor();  
        System.out.println(mintVisitor.getClass().getSimpleName() + " " + es.accept(mintVisitor));
    }  
  
}
           
java複制代碼ArtVisitor 列印廣告 制作銅像 
==========================
MintVisitor 鑄造紙币 鑄造銅币 
           

通路者模式的優點:

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

通路者模式的缺點:

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

10. 中介者模式(Mediator)

定義一個中介對象來封裝一系列對象之間的互動,使原有對象之間的耦合松散,且可以獨立地改變它們之間的互動。中介者模式又叫調停模式,它是迪米特法則的典型應用。

在生活中,當我們租房擷取房源資訊,假設是房東直接和租戶聯系,那麼,當某一個房東有房源資訊時,就需要通知所有的租戶當某一個人需要租房時租房時,就要就要咨詢每一個房東相關房源資訊,這樣子一個發生變化時,很多都會發生變化。

這個時候就可以想想可不可以有一個房屋中介,所有房東将所有的房源資訊告訴中介,這樣子當有租客想要租房時直接找房屋中介,就可以擷取所有的房源資訊了,就不需要咨詢每一個房東了。同樣的,當某個房東有新的房源資訊時,就可以直接告訴房屋中介,就不需要一次告訴所有的需要租房的人了。

抽象工作者(Mediator):定義了中介者對象的接口,負責定義對象間的通信協定。

java複制代碼public interface Mediator {  
  
    void publicHouse(String message);  

    void needHouse(String message);  
  
}
           

抽象同僚類(Colleague):定義了同僚對象的接口,負責定義對象間的行為。

java複制代碼public abstract class Person {  
  
    private String name;  
    private Mediator mediator;  

    public String getName() {  
    return name;  
    }  

    public void setName(String name) {  
    this.name = name;  
    }  

    public Mediator getMediator() {  
    return mediator;  
    }  

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

    public Person(String name) {  
    this.name = name;  
    }  

    /**  
    * 釋出資訊抽象方法,具體的實作有繼承的具體同僚聲明  
    **/  
    public abstract void publishMessage(String msg);  
  
}
           

具體中介者(Concrete Mediator):實作了中介者接口,負責協調各個同僚對象之間的互動關系。

java複制代碼public class HouseAgent implements Mediator {  
  
    private List<HouseOwner> houseOwners = new ArrayList<>();  
    private List<HouseTenant> houseTenants = new ArrayList<>();  

    /**  
    * 中介釋出房屋資訊時,就向所有的租客傳遞房源資訊  
    **/  
    @Override  
    public void publicHouse(String message) {  
        if (!this.houseTenants.isEmpty()) {  
            for (HouseTenant tenant : this.houseTenants) {  
                tenant.contact(message);  
            }  
        }  
    }  

    /**  
    * 當有人需要房子時,中介就向所有的房東傳遞資訊  
    **/  
    @Override  
    public void needHouse(String message) {  
        if (!this.houseOwners.isEmpty()) {  
            for (HouseOwner owner : this.houseOwners) {  
                owner.contact(message);  
            }  
        }  
    }  

    public void setHouseOwners(HouseOwner... houseOwners) {  
        this.houseOwners.addAll(Arrays.asList(houseOwners));  
    }  

    public void setHouseTenants(HouseTenant... houseTenants) {  
        this.houseTenants.addAll(Arrays.asList(houseTenants));  
    }  
  
}
           

具體同僚類(Concrete Colleague):實作了同僚接口,負責實作具體的行為。

java複制代碼public class HouseOwner extends Person {  
  
    public HouseOwner(String name) {  
        super(name);  
    }  

    @Override  
    public void publishMessage(String msg) {  
        getMediator().publicHouse(msg);  
    }  

    public void contact(String message) {  
        System.out.println("中介通知房東," + getName() + ":" + message);  
    }  
  
}
           
java複制代碼public class HouseTenant extends Person {  
  
    public HouseTenant(String name) {  
        super(name);  
    }  

    @Override  
    public void publishMessage(String msg) {  
        getMediator().needHouse(msg);  
    }  

    public void contact(String message) {  
        System.out.println("中介通知租戶," + getName() + ":" + message);  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  

    public static void main(String[] args) {  
        // 建立一個中介者  
        HouseAgent mediator = new HouseAgent();  
        // 建立兩個房東資訊  
        HouseOwner owner1 = new HouseOwner("房東1");  
        HouseOwner owner2 = new HouseOwner("房東2");  

        // 建立三個租戶資訊  
        HouseTenant tenant1 = new HouseTenant("租戶1");  
        HouseTenant tenant2 = new HouseTenant("租戶2");  
        HouseTenant tenant3 = new HouseTenant("租戶3");  

        // 向中介者添加房東資訊  
        mediator.setHouseOwners(owner1, owner2);  
        // 向中介者添加租戶資訊  
        mediator.setHouseTenants(tenant1, tenant2, tenant3);  
        // 中介者釋出房源資訊,會依次通知每一個租戶  
        mediator.publicHouse("在清水河有一個一房間,500元 每月");  
        // 中介者釋出租戶需求,會依次通知每一個房東  
        mediator.needHouse("我需要一個清水河的房子");  
    }  

}
           
java複制代碼中介通知租戶,租戶1:在清水河有一個一房間,500元 每月
中介通知租戶,租戶2:在清水河有一個一房間,500元 每月
中介通知租戶,租戶3:在清水河有一個一房間,500元 每月
中介通知房東,房東1:我需要一個清水河的房子
中介通知房東,房東2:我需要一個清水河的房子
           

中介者模式的優點:

  1. 降低了對象之間的耦合性,使得對象易于獨立地被複用。
  2. 将對象間的一對多關聯轉變為一對一的關聯,提高系統的靈活性,使得系統易于維護和擴充。

中介者模式的缺點:

  1. 當同僚類太多時,中介者的職責将很大,它會變得複雜而龐大,以至于系統難以維護。

11. 解釋器模式(Interpreter)

提供了評估語言的文法或表達式的方式,它屬于行為型模式。這種模式實作了一個表達式接口,該接口解釋一個特定的上下文。這種模式被用在 SQL 解析、符号處理引擎等。
  • 主要解決:對于一些固定文法建構一個解釋句子的解釋器。
  • 如何解決:建構文法樹,定義終結符與非終結符。
  • 何時使用:如果一種特定類型的問題發生的頻率足夠高,那麼可能就值得将該問題的各個執行個體表述為一個簡單語言中的句子。這樣就可以建構一個解釋器,該解釋器通過解釋這些句子來解決該問題。

建立一個表達式接口,并建立實作了上述接口的實體類。

java複制代碼public interface Expression {  
  
    boolean interpret(String context);  
  
}
           
java複制代碼public class TerminalExpression implements Expression {  
  
    private String data;  

    public TerminalExpression(String data) {  
        this.data = data;  
    }  

    @Override  
    public boolean interpret(String context) {  
        return context.contains(data);  
    }  
  
}
           
java複制代碼public class AndExpression implements Expression {  
  
    private Expression expr1;  
    private Expression expr2;  

    public AndExpression(Expression expr1, Expression expr2) {  
        this.expr1 = expr1;  
        this.expr2 = expr2;  
    }  

    @Override  
    public boolean interpret(String context) {  
        return expr1.interpret(context) && expr2.interpret(context);  
    }  
  
}
           
java複制代碼public class OrExpression implements Expression {  
  
    private Expression expr1;  
    private Expression expr2;  

    public OrExpression(Expression expr1, Expression expr2) {  
        this.expr1 = expr1;  
        this.expr2 = expr2;  
    }  

    @Override  
    public boolean interpret(String context) {  
        return expr1.interpret(context) || expr2.interpret(context);  
    }  
  
}
           

編寫一個測試類,觀察輸出結果:

java複制代碼public class MainTest {  
  
    // 規則:Robert 和 John 是男性  
    public static Expression getMaleExpression() {  
        Expression robert = new TerminalExpression("Robert");  
        Expression john = new TerminalExpression("John");  
        return new OrExpression(robert, john);  
    }  

    // 規則:Julie 是一個已婚的女性  
    public static Expression getMarriedWomanExpression() {  
        Expression julie = new TerminalExpression("Julie");  
        Expression married = new TerminalExpression("Married");  
        return new AndExpression(julie, married);  
    }  

    public static void main(String[] args) {  
        Expression isMale = getMaleExpression();  
        Expression isMarriedWoman = getMarriedWomanExpression();  

        System.out.println("John 是男性嗎? " + isMale.interpret("John"));  
        System.out.println("Julie 是已經女性嗎? " + isMarriedWoman.interpret("Married Julie"));  
    }  
  
}
           
java複制代碼John 是男性嗎? true
Julie 是已經女性嗎? true
           

解釋器模式的優點:1、可擴充性比較好,靈活。 2、增加了新的解釋表達式的方式。 3、易于實作簡單文法。

解釋器模式的缺點:1、可利用場景比較少。 2、對于複雜的文法比較難維護。 3、解釋器模式會引起類膨脹。 4、解釋器模式采用遞歸調用方法。

作者:白菜說技術

連結:https://juejin.cn/post/7259768679344242746

繼續閱讀