一篇文章帶你瞭解設計模式——行為型模式

来源:https://www.cnblogs.com/qiuluoyuweiliang/archive/2023/02/04/17092043.html
-Advertisement-
Play Games

一篇文章帶你瞭解設計模式——行為型模式 在之前的文章我們已經介紹了設計模式中的創建者模式和結構型模式,下麵我們來介紹最後一部分行為型模式 行為型模式用於描述程式在運行時複雜的流程式控制制,即描述多個類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務 行為型模式分為類行為模式和對象行為模式,前者 ...


一篇文章帶你瞭解設計模式——行為型模式

在之前的文章我們已經介紹了設計模式中的創建者模式和結構型模式,下麵我們來介紹最後一部分行為型模式

行為型模式用於描述程式在運行時複雜的流程式控制制,即描述多個類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務

行為型模式分為類行為模式和對象行為模式,前者採用繼承機制來在類間分派行為,後者採用組合或聚合在對象間分配行為。

由於組合關係或聚合關係比繼承關係耦合度低,滿足“合成復用原則”,所以對象行為模式比類行為模式具有更大的靈活性。

下麵我們將介紹十一種行為型模式:

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 責任鏈模式
  • 狀態模式
  • 觀察者模式
  • 中介者模式
  • 迭代器模式
  • 訪問者模式
  • 解釋器模式

模板方法模式

首先我們來介紹模板方法模式

模板方法模式簡述

首先我們給出模板方法模式的概念:

  • 定義一個操作中的演算法骨架
  • 將演算法的一些步驟延遲到子類中,使得子類可以不改變該演算法結構的情況下重定義該演算法的某些特定步驟。

模板方法模式結構

模板方法(Template Method)模式包含以下主要角色:

  • 抽象類(Abstract Class):負責給出一個演算法的輪廓和骨架。它由一個模板方法和若幹個基本方法構成。

    • 模板方法:定義了演算法的骨架,按某種順序調用其包含的基本方法。

    • 基本方法:是實現演算法各個步驟的方法,是模板方法的組成部分。基本方法又可以分為三種:

      • 抽象方法(Abstract Method) :一個抽象方法由抽象類聲明、由其具體子類實現。

      • 具體方法(Concrete Method) :一個具體方法由一個抽象類或具體類聲明並實現,其子類可以進行覆蓋也可以直接繼承。

      • 鉤子方法(Hook Method) :在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。

        一般鉤子方法是用於判斷的邏輯方法,這類方法名一般為isXxx,返回值類型為boolean類型。

  • 具體子類(Concrete Class):實現抽象類中所定義的抽象方法和鉤子方法,它們是一個頂級邏輯的組成步驟。

模板方法模式案例

我們給出一個簡單的例子來介紹模板方法模式:

具體分析:

/*

【例】炒菜

炒菜的步驟是固定的,分為倒油、熱油、倒蔬菜、倒調料品、翻炒等步驟。現通過模板方法模式來用代碼模擬。

上述的AbstractClass就是抽象類,我們在抽象類給出一個模板方法cookProcess,裡面會給出其他基本方法的執行順序,部分基本方法會有具體內容,部分基本方法屬於Abstract方法,由子類去實現

下麵的ConcreteClass_BaoCai和ConcreteClass_CaiXin屬於子類實現類,他們會繼承父類的模板方法,同時重寫抽象基本方法完成自己的需求

*/

/* 具體代碼 */

// 抽象類
public abstract class AbstractClass {
    
    // 模板方法(為防止惡意操作,一般模板方法都加上 final 關鍵詞)
    public final void cookProcess() {
        //第一步:倒油
        this.pourOil();
        //第二步:熱油
        this.heatOil();
        //第三步:倒蔬菜
        this.pourVegetable();
        //第四步:倒調味料
        this.pourSauce();
        //第五步:翻炒
        this.fry();
    }

    // 下述均為基本方法
    
    public void pourOil() {
        System.out.println("倒油");
    }

    //第二步:熱油是一樣的,所以直接實現
    public void heatOil() {
        System.out.println("熱油");
    }

    //第三步:倒蔬菜是不一樣的(一個下包菜,一個是下菜心)
    public abstract void pourVegetable();

    //第四步:倒調味料是不一樣
    public abstract void pourSauce();


    //第五步:翻炒是一樣的,所以直接實現
    public void fry(){
        System.out.println("炒啊炒啊炒到熟啊");
    }
}

// Baocai實現類
public class ConcreteClass_BaoCai extends AbstractClass {

    @Override
    public void pourVegetable() {
        System.out.println("下鍋的蔬菜是包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println("下鍋的醬料是辣椒");
    }
}

// Caixin實現類
public class ConcreteClass_CaiXin extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("下鍋的蔬菜是菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("下鍋的醬料是蒜蓉");
    }
}

public class Client {
    public static void main(String[] args) {
        //炒手撕包菜
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        baoCai.cookProcess();

        //炒蒜蓉菜心
        ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
        caiXin.cookProcess();
    }
}

模板方法模式分析

首先我們給出模板方法模式的適用場景:

  • 演算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現。
  • 需要通過子類來決定父類演算法中某個步驟是否執行,實現子類對父類的反向控制。

然後我們給出模板方法模式的優點:

  • 提高代碼復用性

    將相同部分的代碼放在抽象的父類中,而將不同的代碼放入不同的子類中。

  • 實現了反向控制

    通過一個父類調用其子類的操作,通過對子類的具體實現擴展不同的行為,實現了反向控制 ,並符合“開閉原則”。

最後我們給出模板方法模式的缺點:

  • 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
  • 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。

策略模式

下麵我們來介紹策略模式

策略模式簡述

首先我們給出策略模式的概念:

  • 策略模式和模板模式其實比較相似,只不過前者使用聚合,後者使用繼承。

  • 該模式定義了一系列演算法,並將每個演算法封裝起來,使它們可以相互替換,且演算法的變化不會影響使用演算法的客戶。

  • 對象行為模式,它通過對演算法進行封裝,把使用演算法的責任和演算法的實現分割開來,並委派給不同的對象對這些演算法進行管理。

我們給出一個簡單的例子說明:

  • 在日常工作種,我們開發需要選擇一款開發工具,當然可以進行代碼開發的工具有很多
  • 可以選擇Idea進行開發,也可以使用eclipse進行開發,也可以使用其他的一些開發工具,這些工具就是策略

策略模式結構

策略模式的主要角色如下:

  • 抽象策略(Strategy)類:這是一個抽象角色,通常由一個介面或抽象類實現。此角色給出所有的具體策略類所需的介面。
  • 具體策略(Concrete Strategy)類:實現了抽象策略定義的介面,提供具體的演算法實現或行為。
  • 環境(Context)類:持有一個策略類的引用,最終給客戶端調用。

策略模式案例

我們同樣給出一個簡單的案例講解策略模式:

具體分析:

/*

【例】促銷活動

一家百貨公司在定年度的促銷活動。針對不同的節日(春節、中秋節、聖誕節)推出不同的促銷活動,由促銷員將促銷活動展示給客戶。

其中SalesMan就是環境類,Strategy是抽象策略類,下麵的方案就是具體策略類

SalesMan中聚合一個Strategy,然後會用子類去填充,具有一定格式但不同實現的子類就可以不斷更替Strategy而實現策略更換

*/

/* 代碼展示 */

// 環境類(調用更換策略類)
public class SalesMan {                        
    //持有抽象策略角色的引用                              
    private Strategy strategy;                 
                                               
    public SalesMan(Strategy strategy) {       
        this.strategy = strategy;              
    }                                          
                                               
    //向客戶展示促銷活動                                
    public void salesManShow(){                
        strategy.show();                       
    }                                          
}

// 抽象策略類
public interface Strategy {
    void show();
}

// 具體策略類

//為春節準備的促銷活動A
public class StrategyA implements Strategy {

    public void show() {
        System.out.println("買一送一");
    }
}

//為中秋準備的促銷活動B
public class StrategyB implements Strategy {

    public void show() {
        System.out.println("滿200元減50元");
    }
}

//為聖誕準備的促銷活動C
public class StrategyC implements Strategy {

    public void show() {
        System.out.println("滿1000元加一元換購任意200元以下商品");
    }
}

策略模式分析

首先我們給出策略模式的適用場景:

  • 系統中各演算法彼此完全獨立,且要求對客戶隱藏具體演算法的實現細節時。
  • 一個系統需要動態地在幾種演算法中選擇一種時,可將每個演算法封裝到策略類中。
  • 多個類只區別在表現行為不同,可以使用策略模式,在運行時動態選擇具體要執行的行為。
  • 系統要求使用演算法的客戶不應該知道其操作的數據時,可使用策略模式來隱藏與演算法相關的數據結構。
  • 一個類定義了多種行為,並且這些行為在這個類的操作中以多個條件語句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。

然後我們給出策略模式的優點:

  • 策略類之間可以自由切換

    由於策略類都實現同一個介面,所以使它們之間可以自由切換。

  • 易於擴展

    增加一個新的策略只需要添加一個具體的策略類即可,基本不需要改變原有的代碼,符合“開閉原則“

  • 避免使用多重條件選擇語句(if else),充分體現面向對象設計思想。

最後我們給出策略模式的缺點:

  • 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。
  • 策略模式將造成產生很多策略類,可以通過使用享元模式在一定程度上減少對象的數量。

命令模式

下麵我們來介紹命令模式

命令模式簡述

首先我們給出命令模式的概念:

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

我們給出一個簡單示例:

  • 我們在餐廳吃飯,我們給出的訂單就是命令,我們將訂單封裝為一個對象,這就是請求的責任
  • 我們給出的訂單會有大廚去完成,那麼大廚就是執行請求的對象
  • 然後在這之間會有服務員,服務員就是一個命令對象,她會負責將請求和執行兩方面保存並傳遞調用

命令模式結構

命令模式包含以下主要角色:

  • 抽象命令類(Command)角色: 定義命令的介面,聲明執行的方法。
  • 具體命令(Concrete Command)角色:具體的命令,實現命令介面;通常會持有接收者,並調用接收者的功能來完成命令要執行的操作。
  • 實現者/接收者(Receiver)角色: 接收者,真正執行命令的對象。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。
  • 調用者/請求者(Invoker)角色: 要求命令對象執行請求,通常會持有命令對象,可以持有很多的命令對象。這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令對象的入口。

命令模式案例

我們給出一個簡單的案例來介紹命令模式:

具體分析:

/*

將上面的案例用代碼實現,那我們就需要分析命令模式的角色在該案例中由誰來充當。

服務員: 就是調用者角色,由她來發起命令。

資深大廚: 就是接收者角色,真正命令執行的對象。

訂單: 命令中包含訂單。

註意:

訂單就是命令,命令是自己具有的,有指定的接收對象

服務員只負責將啟動命令,實則還是調用命令內部的方法

*/

/* 代碼展示 */

// 抽象命令類
public interface Command {
    void execute();//只需要定義一個統一的執行方法
}

// 具體命令類
public class OrderCommand implements Command {

    // 持有接受者對象(就是實現者,當調用者調用命令後實現者會去執行該命令)
    private SeniorChef receiver;
    
    // 執行的內容存儲
    private Order order;

    public OrderCommand(SeniorChef receiver, Order order){
        this.receiver = receiver;
        this.order = order;
    }

    // 具體的命令執行,調用者只負責調用該方法
    public void execute()  {
        System.out.println(order.getDiningTable() + "桌的訂單:");
        Set<String> keys = order.getFoodDic().keySet();
        for (String key : keys) {
            receiver.makeFood(order.getFoodDic().get(key),key);
        }

        try {
            Thread.sleep(100);//停頓一下 模擬做飯的過程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        System.out.println(order.getDiningTable() + "桌的飯弄好了");
    }
}

// domain實體類
public class Order {
    // 餐桌號碼
    private int diningTable;

    // 用來存儲餐名並記錄份數
    private Map<String, Integer> foodDic = new HashMap<String, Integer>();

    public int getDiningTable() {
        return diningTable;
    }

    public void setDiningTable(int diningTable) {
        this.diningTable = diningTable;
    }

    public Map<String, Integer> getFoodDic() {
        return foodDic;
    }

    public void setFoodDic(String name, int num) {
        foodDic.put(name,num);
    }
}

// 資深大廚類 是命令的Receiver
public class SeniorChef {

    public void makeFood(int num,String foodName) {
        System.out.println(num + "份" + foodName);
    }
}

// 調用者,負責協調請求和接收者
public class Waitor {

    private ArrayList<Command> commands;//可以持有很多的命令對象

    public Waitor() {
        commands = new ArrayList();
    }
    
    public void setCommand(Command cmd){
        commands.add(cmd);
    }

    // 發出命令 喊 訂單來了,廚師開始執行
    public void orderUp() {
        System.out.println("美女服務員:叮咚,大廚,新訂單來了.......");
        for (int i = 0; i < commands.size(); i++) {
            Command cmd = commands.get(i);
            if (cmd != null) {
                cmd.execute();
            }
        }
    }
}

// 測試類
public class Client {
    public static void main(String[] args) {
        //創建2個order
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.getFoodDic().put("西紅柿雞蛋面",1);
        order1.getFoodDic().put("小杯可樂",2);

        Order order2 = new Order();
        order2.setDiningTable(3);
        order2.getFoodDic().put("尖椒肉絲蓋飯",1);
        order2.getFoodDic().put("小杯雪碧",1);

        //創建接收者
        SeniorChef receiver=new SeniorChef();
        //將訂單和接收者封裝成命令對象
        OrderCommand cmd1 = new OrderCommand(receiver, order1);
        OrderCommand cmd2 = new OrderCommand(receiver, order2);
        //創建調用者 waitor
        Waitor invoker = new Waitor();
        invoker.setCommand(cmd1);
        invoker.setCommand(cmd2);

        //將訂單帶到櫃臺 並向廚師喊 訂單來了
        invoker.orderUp();
    }
}

命令模式分析

我們首先給出命令模式的適用場景:

  • 系統需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。
  • 系統需要在不同的時間指定請求、將請求排隊和執行請求。
  • 系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作。

然後我們給出命令模式的優點:

  • 降低系統的耦合度。命令模式能將調用操作的對象與實現該操作的對象解耦。
  • 增加或刪除命令非常方便。採用命令模式增加與刪除命令不會影響其他類,它滿足“開閉原則”,對擴展比較靈活。
  • 可以實現巨集命令。命令模式可以與組合模式結合,將多個命令裝配成一個組合命令,即巨集命令。
  • 方便實現 Undo 和 Redo 操作。命令模式可以與後面介紹的備忘錄模式結合,實現命令的撤銷與恢復。

最後我們給出命令模式的缺點:

  • 使用命令模式可能會導致某些系統有過多的具體命令類。
  • 系統結構更加複雜。

責任鏈模式

下麵我們來介紹責任鏈模式

責任鏈模式簡述

首先我們先來簡單介紹一下責任鏈模式:

  • 為了避免請求發送者與多個請求處理者耦合在一起,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈;
  • 當有請求發生時,可將請求沿著這條鏈傳遞,直到有對象處理它為止

我們給出一個簡單例子:

  • 我們身為一個小員工,如果請假需要經過上級的同意
  • 但不同的上級具有不同的許可權,例如小組長簽一天,部門經理簽三天,總經理簽七天
  • 正常情況下,我們需要記住每個領導,自己根據假期時間找各個領導,導致我們和每個領導都需要有關係,耦合性過大

責任鏈模式結構

職責鏈模式主要包含以下角色:

  • 抽象處理者(Handler)角色:定義一個處理請求的介面,包含抽象處理方法和一個後繼連接。
  • 具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的後繼者。
  • 客戶類(Client)角色:創建處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。

責任鏈模式案例

我們同樣給出一個簡單案例來講解責任鏈模式:

具體分析:

/*

【例】

現需要開發一個請假流程式控制制系統。

請假一天以下的假只需要小組長同意即可;請假1天到3天的假還需要部門經理同意;請求3天到7天還需要總經理同意才行。

LeaveRequest:請假條,記錄任命,日期,信息;屬於實體類

Handler:抽象處理者

Leader:具體處理者

*/

/* 代碼展示 */

// 請假條(實體類,僅用於記錄信息)
public class LeaveRequest {
    private String name;//姓名
    private int num;//請假天數
    private String content;//請假內容

    public LeaveRequest(String name, int num, String content) {
        this.name = name;
        this.num = num;
        this.content = content;
    }

    public String getName() {
        return name;
    }

    public int getNum() {
        return num;
    }

    public String getContent() {
        return content;
    }
}

// 處理者抽象類
public abstract class Handler {
    
    // 請假日期分界線,用於子類使用
    protected final static int NUM_ONE = 1;
    protected final static int NUM_THREE = 3;
    protected final static int NUM_SEVEN = 7;

    // 該領導處理的請假天數區間
    private int numStart;
    private int numEnd;

    // 領導上面還有領導(責任鏈的下一位)
    private Handler nextHandler;

    // 設置請假天數範圍 上不封頂
    public Handler(int numStart) {
        this.numStart = numStart;
    }

    // 設置請假天數範圍
    public Handler(int numStart, int numEnd) {
        this.numStart = numStart;
        this.numEnd = numEnd;
    }

    // 設置上級領導
    public void setNextHandler(Handler nextHandler){
        this.nextHandler = nextHandler;
    }

    // 提交請假條(這裡是一個提交方法,參數為leaveRequest,主要是給子類的領導層使用的)
    public final void submit(LeaveRequest leave){
        
        // 首先判斷是否請假
        if(0 == this.numStart){
            return;
        }

        //如果請假天數達到該領導者的處理要求
        if(leave.getNum() >= this.numStart){
            this.handleLeave(leave);

            //如果還有上級 並且請假天數超過了當前領導的處理範圍
            if(null != this.nextHandler && leave.getNum() > numEnd){
                this.nextHandler.submit(leave);//繼續提交
            } else {
                System.out.println("流程結束");
            }
        }
    }

    // 各級領導處理請假條方法
    protected abstract void handleLeave(LeaveRequest leave);
}

// 小組長
public class GroupLeader extends Handler {
    public GroupLeader() {
        //小組長處理1-3天的請假
        super(Handler.NUM_ONE, Handler.NUM_THREE);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "請假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("小組長審批:同意。");
    }
}

// 部門經理
public class Manager extends Handler {
    public Manager() {
        //部門經理處理3-7天的請假
        super(Handler.NUM_THREE, Handler.NUM_SEVEN);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "請假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("部門經理審批:同意。");
    }
}

// 總經理
public class GeneralManager extends Handler {
    public GeneralManager() {
        //部門經理處理7天以上的請假
        super(Handler.NUM_SEVEN);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "請假" + leave.getNum() + "天," + leave.getContent() + "。");
        System.out.println("總經理審批:同意。");
    }
}

// 測試類
public class Client {
    public static void main(String[] args) {
        //請假條來一張
        LeaveRequest leave = new LeaveRequest("小花",5,"身體不適");

        //各位領導
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();

        groupLeader.setNextHandler(manager);//小組長的領導是部門經理
        manager.setNextHandler(generalManager);//部門經理的領導是總經理
        //之所以在這裡設置上級領導,是因為可以根據實際需求來更改設置,如果實戰中上級領導人都是固定的,則可以移到領導實現類中。

        //提交申請
        groupLeader.submit(leave);
    }
}

責任鏈模式分析

首先我們給出責任鏈模式的優點:

  • 降低了對象之間的耦合度

    該模式降低了請求發送者和接收者的耦合度。

  • 增強了系統的可擴展性

    可以根據需要增加新的請求處理類,滿足開閉原則。

  • 增強了給對象指派職責的靈活性

    當工作流程發生變化,可以動態地改變鏈內的成員或者修改它們的次序,也可動態地新增或者刪除責任。

  • 責任鏈簡化了對象之間的連接

    一個對象只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if···else 語句。

  • 責任分擔

    每個類只需要處理自己該處理的工作,不能處理的傳遞給下一個對象完成,明確各類的責任範圍,符合類的單一職責原則。

然後我們給出責任鏈模式的缺點:

  • 對比較長的職責鏈,請求的處理可能涉及多個處理對象,系統性能將受到一定影響
  • 不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理
  • 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設置而導致系統出錯

狀態模式

下麵我們來介紹狀態模式

狀態模式簡述

首先我們給出狀態模式的概念:

  • 對有狀態的對象,把複雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行為。

狀態模式結構

狀態模式包含以下主要角色。

  • 環境(Context)角色:也稱為上下文,它定義了客戶程式需要的介面,維護一個當前狀態,並將與狀態相關的操作委托給當前狀態對象來處理。
  • 抽象狀態(State)角色:定義一個介面,用以封裝環境對象中的特定狀態所對應的行為。
  • 具體狀態(Concrete State)角色:實現抽象狀態所對應的行為。

狀態模式案例

我們首先給出一個非狀態模式:

具體分析:

/*

【例】通過按鈕來控制一個電梯的狀態,一個電梯有開門狀態,關門狀態,停止狀態,運行狀態。每一種狀態改變,都有可能要根據其他狀態來更新處理。例如,如果電梯門現在處於運行時狀態,就不能進行開門操作,而如果電梯門是停止狀態,就可以執行開門操作。

下述代碼問題:
- 使用了大量的switch…case這樣的判斷(if…else也是一樣),使程式的可閱讀性變差。
- 擴展性很差。如果新加了斷電的狀態,我們需要修改上面判斷邏輯

*/

/* 代碼展示 */

public interface ILift {
    //電梯的4個狀態
    //開門狀態
    public final static int OPENING_STATE = 1;
    //關門狀態
    public final static int CLOSING_STATE = 2;
    //運行狀態
    public final static int RUNNING_STATE = 3;
    //停止狀態
    public final static int STOPPING_STATE = 4;

    //設置電梯的狀態
    public void setState(int state);

    //電梯的動作
    public void open();
    public void close();
    public void run();
    public void stop();
}

public class Lift implements ILift {
    private int state;

    @Override
    public void setState(int state) {
        this.state = state;
    }

    //執行關門動作
    @Override
    public void close() {
        switch (this.state) {
            case OPENING_STATE:
                System.out.println("電梯關門了。。。");//只有開門狀態可以關閉電梯門,可以對應電梯狀態表來看
                this.setState(CLOSING_STATE);//關門之後電梯就是關閉狀態了
                break;
            case CLOSING_STATE:
                //do nothing //已經是關門狀態,不能關門
                break;
            case RUNNING_STATE:
                //do nothing //運行時電梯門是關著的,不能關門
                break;
            case STOPPING_STATE:
                //do nothing //停止時電梯也是關著的,不能關門
                break;
        }
    }

    //執行開門動作
    @Override
    public void open() {
        switch (this.state) {
            case OPENING_STATE://門已經開了,不能再開門了
                //do nothing
                break;
            case CLOSING_STATE://關門狀態,門打開:
                System.out.println("電梯門打開了。。。");
                this.setState(OPENING_STATE);
                break;
            case RUNNING_STATE:
                //do nothing 運行時電梯不能開門
                break;
            case STOPPING_STATE:
                System.out.println("電梯門開了。。。");//電梯停了,可以開門了
                this.setState(OPENING_STATE);
                break;
        }
    }

    //執行運行動作
    @Override
    public void run() {
        switch (this.state) {
            case OPENING_STATE://電梯不能開著門就走
                //do nothing
                break;
            case CLOSING_STATE://門關了,可以運行了
                System.out.println("電梯開始運行了。。。");
                this.setState(RUNNING_STATE);//現在是運行狀態
                break;
            case RUNNING_STATE:
                //do nothing 已經是運行狀態了
                break;
            case STOPPING_STATE:
                System.out.println("電梯開始運行了。。。");
                this.setState(RUNNING_STATE);
                break;
        }
    }

    //執行停止動作
    @Override
    public void stop() {
        switch (this.state) {
            case OPENING_STATE: //開門的電梯已經是是停止的了(正常情況下)
                //do nothing
                break;
            case CLOSING_STATE://關門時才可以停止
                System.out.println("電梯停止了。。。");
                this.setState(STOPPING_STATE);
                break;
            case RUNNING_STATE://運行時當然可以停止了
                System.out.println("電梯停止了。。。");
                this.setState(STOPPING_STATE);
                break;
            case STOPPING_STATE:
                //do nothing
                break;
        }
    }
}

public class Client {
    public static void main(String[] args) {
        Lift lift = new Lift();
        lift.setState(ILift.STOPPING_STATE);//電梯是停止的
        lift.open();//開門
        lift.close();//關門
        lift.run();//運行
        lift.stop();//停止
    }
}

然後我們給出狀態模式下的修改案例:

具體分析:

/*

對上述電梯的案例使用狀態模式進行改進

*/

/* 代碼展示 */

//抽象狀態類
public abstract class LiftState {
    //定義一個環境角色,也就是封裝狀態的變化引起的功能變化
    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    //電梯開門動作
    public abstract void open();

    //電梯關門動作
    public abstract void close();

    //電梯運行動作
    public abstract void run();

    //電梯停止動作
    public abstract void stop();
}

//開啟狀態
public class OpenningState extends LiftState {

    //開啟當然可以關閉了,我就想測試一下電梯門開關功能
    @Override
    public void open() {
        System.out.println("電梯門開啟...");
    }

    @Override
    public void close() {
        //狀態修改
        super.context.setLiftState(Context.closeingState);
        //動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
        super.context.getLiftState().close();
    }

    //電梯門不能開著就跑,這裡什麼也不做
    @Override
    public void run() {
        //do nothing
    }

    //開門狀態已經是停止的了
    @Override
    public void stop() {
        //do nothing
    }
}

//運行狀態
public class RunningState extends LiftState {

    //運行的時候開電梯門?你瘋了!電梯不會給你開的
    @Override
    public void open() {
        //do nothing
    }

    //電梯門關閉?這是肯定了
    @Override
    public void close() {//雖然可以關門,但這個動作不歸我執行
        //do nothing
    }

    //這是在運行狀態下要實現的方法
    @Override
    public void run() {
        System.out.println("電梯正在運行...");
    }

    //這個事絕對是合理的,光運行不停止還有誰敢做這個電梯?!估計只有上帝了
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState);
        super.context.stop();
    }
}

//停止狀態
public class StoppingState extends LiftState {

    //停止狀態,開門,那是要的!
    @Override
    public void open() {
        //狀態修改
        super.context.setLiftState(Context.openningState);
        //動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
        super.context.getLiftState().open();
    }

    @Override
    public void close() {//雖然可以關門,但這個動作不歸我執行
        //狀態修改
        super.context.setLiftState(Context.closeingState);
        //動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
        super.context.getLiftState().close();
    }

    //停止狀態再跑起來,正常的很
    @Override
    public void run() {
        //狀態修改
        super.context.setLiftState(Context.runningState);
        //動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作
        super.context.getLiftState().run();
    }

    //停止狀態是怎麼發生的呢?當然是停止方法執行了
    @Override
    public void stop() {
        System.out.println("電梯停止了...");
    }
}

//關閉狀態
public class ClosingState extends LiftState {

    @Override
    //電梯門關閉,這是關閉狀態要實現的動作
    public void close() {
        System.out.println("電梯門關閉...");
    }

    //電梯門關了再打開,逗你玩呢,那這個允許呀
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState);
        super.context.open();
    }


    //電梯門關了就跑,這是再正常不過了
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState);
        super.context.run();
    }

    //電梯門關著,我就不按樓層
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState);
        super.context.stop();
    }
}

//環境角色
public class Context {
    //定義出所有的電梯狀態
    public final static OpenningState openningState = new OpenningState();//開門狀態,這時候電梯只能關閉
    public final static ClosingState closeingState = new ClosingState();//關閉狀態,這時候電梯可以運行、停止和開門
    public final static RunningState runningState = new RunningState();//運行狀態,這時候電梯只能停止
    public final static StoppingState stoppingState = new StoppingState();//停止狀態,這時候電梯可以開門、運行


    //定義一個當前電梯狀態
    private LiftState liftState;

    public LiftState getLiftState() {
        return this.liftState;
    }

    public void setLiftState(LiftState liftState) {
        //當前環境改變
        this.liftState = liftState;
        //把當前的環境通知到各個實現類中
        this.liftState.setContext(this);
    }

    public void open() {
        this.liftState.open();
    }

    public void close() {
        this.liftState.close();
    }

    public void run() {
        this.liftState.run();
    }

    public void stop() {
        this.liftState.stop();
    }
}

//測試類
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.setLiftState(new ClosingState());

        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

狀態模式分析

我們首先給出狀態模式的適用場景:

  • 當一個對象的行為取決於它的狀態,並且它必須在運行時根據狀態改變它的行為時,就可以考慮使用狀態模式。
  • 一個操作中含有龐大的分支結構,並且這些分支決定於對象的狀態時。

然後我們給出狀態模式的優點:

  • 將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
  • 允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。

最後我們給出狀態模式的缺點:

  • 狀態模式的使用必然會增加系統類和對象的個數。
  • 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和代碼的混亂。
  • 狀態模式對"開閉原則"的支持並不太好。

觀察者模式

下麵我們來介紹觀察者模式

觀察者模式簡述

首先我們給出觀察者模式的概念:

  • 又被稱為發佈-訂閱(Publish/Subscribe)模式,它定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。
  • 這個主題對象在狀態變化時,會通知所有的觀察者對象,使他們能夠自動更新自己。

觀察者模式結構

在觀察者模式中有如下角色:

  • Subject:抽象主題(抽象被觀察者),抽象主題角色把所有觀察者對象保存在一個集合里,每個主題都可以有任意數量的觀察者,抽象主題提供一個介面,可以增加和刪除觀察者對象。
  • ConcreteSubject:具體主題(具體被觀察者),該角色將有關狀態存入具體觀察者對象,在具體主題的內部狀態發生改變時,給所有註冊過的觀察者發送通知。
  • Observer:抽象觀察者,是觀察者的抽象類,它定義了一個更新介面,使得在得到主題更改通知時更新自己。
  • ConcrereObserver:具體觀察者,實現抽象觀察者定義的更新介面,以便在得到主題更改通知時更新自身的狀態。

觀察者模式案例

我們通過一個案例來解釋觀察者模式:

具體分析:

/*

【例】微信公眾號

在使用微信公眾號時,大家都會有這樣的體驗,當你關註的公眾號中有新內容更新的話,它就會推送給關註公眾號的微信用戶端。
我們使用觀察者模式來模擬這樣的場景,微信用戶就是觀察者,微信公眾號是被觀察者,有多個的微信用戶關註了這個公眾號。

其中微信公眾號就是被觀察者,被觀察者可以存儲多個觀察者,當被觀察者做出一些修改後,就會調用一個方法去通知觀察者並修改內容

*/

/* 代碼展示 */

// 抽象觀察者(觀察者中具有一個修改方法,當被觀察者被修改後,會導致觀察者調用update方法)
public interface Observer {
    void update(String message);
}

// 具體觀察者(這裡是指用戶,這裡指wx公眾號發表文章後,觀察者會收到一條提示信息)
public class WeixinUser implements Observer {
    // 微信用戶名
    private String name;

    public WeixinUser(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + "-" + message);
    }
}

// 抽象主題類(被觀察者)
public interface Subject {
    
    // 增加訂閱者
    public void attach(Observer observer);

    // 刪除訂閱者
    public void detach(Observer observer);
    
    // 通知訂閱者更新消息
    public void notify(String message);
}

// 具體主題類(被觀察者)
public class SubscriptionSubject implements Subject {
    
    // 儲存訂閱公眾號的微信用戶
    private List<Observer> weixinUserlist = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);
    }

    // 當wx公眾號發表文章,就會調用該方法,然後通知各個觀察者去update
    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

// 客戶端信息
public class Client {
    public static void main(String[] args) {
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
        //創建微信用戶
        WeixinUser user1=new WeixinUser("孫悟空");
        WeixinUser user2=new WeixinUser("豬悟能");
        WeixinUser user3=new WeixinUser("沙悟凈");
        //訂閱公眾號
        mSubscriptionSubject.attach(user1);
        mSubscriptionSubject.attach(user2);
        mSubscriptionSubject.attach(user3);
        //公眾號更新發出消息給訂閱的微信用戶
        mSubscriptionSubject.notify("傳智黑馬的專欄更新了");
    }
}

觀察者模式分析

首先我們給出觀察者模式的適用場景:

  • 對象間存在一對多關係,一個對象的狀態發生改變會影響其他對象。
  • 當一個抽象模型有兩個方面,其中一個方面依賴於另一方面時。

然後我們給出觀察者模式的優點:

  • 降低了目標與觀察者之間的耦合關係,兩者之間是抽象耦合關係。
  • 被觀察者發送通知,所有註冊的觀察者都會收到信息【可以實現廣播機制】

最後我們給出觀察者模式的缺點:

  • 如果觀察者非常多的話,那麼所有的觀察者收到被觀察者發送的通知會耗時
  • 如果被觀察者有迴圈依賴的話,那麼被觀察者發送通知會使觀察者迴圈調用,會導致系統崩潰

中介者模式

下麵我們來介紹中介者模式

中介者模式簡述

首先我們給出中介者模式的概念:

  • 又叫調停模式,定義一個中介角色來封裝一系列對象之間的交互,使原有對象之間的耦合鬆散,且可以獨立地改變它們之間的交互。

我們給出一個簡單的示例:

  • 假設我們的工作組有六個人,我們其中只要有一個發生改變,那麼其他人對於該員工的所有屬性方法都需要去更換
  • 但是如果我們有一個中介者,我們與其他人的交際本身就只有中介者,那麼即使一個員工發生改變,也只有該員工和中介者需要修改

中介者模式結構

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

  • 抽象中介者(Mediator)角色:它是中介者的介面,提供了同事對象註冊與轉發同事對象信息的抽象方法。

  • 具體中介者(ConcreteMediator)角色:實現中介者介面,定義一個 List 來管理同事對象,協調各個同事角色之間的交互關係,因此它依賴於同事角色。

  • 抽象同事類(Colleague)角色:定義同事類的介面,保存中介者對象,提供同事對象交互的抽象方法,實現所有相互影響的同事類的公共功能。

  • 具體同事類(Concrete Colleague)角色:是抽象同事類的實現者,當需要與其他同事對象交互時,由中介者對象負責後續的交互。

中介者模式案例

我們通過一個簡單的案例來介紹中介者模式:

具體分析:

/*

【例】租房

現在租房基本都是通過房屋中介,房主將房屋托管給房屋中介,而租房者從房屋中介獲取房屋信息。房屋中介充當租房者與房屋所有者之間的中介者。

租房者本身需要和所有房東聯繫才能獲得房屋信息,但是可以通過中介獲得所有房屋信息,這就實現瞭解耦操作

*/

/* 代碼展示 */

// 抽象中介者
public abstract class Mediator {
    
    //申明一個聯絡方法(前者是信息,後者是信息發送人)
    public abstract void constact(String message,Person person);
}

// 抽象同事類
public abstract class Person {
    
    protected String name;
    protected Mediator mediator;

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

// 具體同事類 房屋擁有者
public class HouseOwner extends Person {

    public HouseOwner(String name, Mediator mediator) {
        super(name, mediator);
    }

    //與中介者聯繫
    public void constact(String message){
        mediator.constact(message, this);
    }

    //獲取信息
    public void getMessage(String message){
        System.out.println("房主" + name +"獲取到的信息:" + message);
    }
}

// 具體同事類 承租人
public class Tenant extends Person {
    public Tenant(String name, Mediator mediator) {
        super(name, mediator);
    }

    //與中介者聯繫
    public void constact(String message){
        mediator.constact(message, this);
    }

    //獲取信息
    public void getMessage(String message){
        System.out.println("租房者" + name +"獲取到的信息:" + message);
    }
}

//中介機構
public class MediatorStructure extends Mediator {
    //首先中介結構必須知道所有房主和租房者的信息
    private HouseOwner houseOwner;
    private Tenant tenant;

    public HouseOwner getHouseOwner() {
        return houseOwner;
    }

    public void setHouseOwner(HouseOwner houseOwner) {
        this.houseOwner = houseOwner;
    }

    public Tenant getTenant() {
        return tenant;
    }

    public void setTenant(Tenant tenant) {
        this.tenant = tenant;
    }

    public void constact(String message, Person person) {
        // 如果是房主進行聯繫,則對應的租房者獲得房主的信息
        if (person == houseOwner) {          
            tenant.getMessage(message);
        } else {	//反之則是房主獲得信息       
            houseOwner.getMessage(message);
        }
    }
}

//測試類
public class Client {
    public static void main(String[] args) {
        //一個房主、一個租房者、一個中介機構
        MediatorStructure mediator = new MediatorStructure();

        //房主和租房者只需要知道中介機構即可
        HouseOwner houseOwner = new HouseOwner("張三", mediator);
        Tenant tenant = new Tenant("李四", mediator);

        //中介結構要知道房主和租房者
        mediator.setHouseOwner(houseOwner);
        mediator.setTenant(tenant);

        tenant.constact("需要租三室的房子");
        houseOwner.constact("我這有三室的房子,你需要租嗎?");
    }
}

中介者模式分析

首先我們給出中介者模式的適用場景:

  • 系統中對象之間存在複雜的引用關係,系統結構混亂且難以理解。
  • 當想創建一個運行於多個類之間的對象,又不想生成新的子類時。

然後我們給出中介者模式的優點:

  • 鬆散耦合

    中介者模式通過把多個同事對象之間的交互封裝到中介者對象裡面,從而使得同事對象之間鬆散耦合,基本上可以做到互補依賴。這樣一來,同事對象就可以獨立地變化和復用,而不再像以前那樣“牽一處而動全身”了。

  • 集中控制交互

    多個同事對象的交互,被封裝在中介者對象裡面集中管理,使得這些交互行為發生變化的時候,只需要修改中介者對象就可以了,當然如果是已經做好的系統,那麼就擴展中介者對象,而各個同事類不需要做修改。

  • 一對多關聯轉變為一對一的關聯

    沒有使用中介者模式的時候,同事對象之間的關係通常是一對多的,引入中介者對象以後,中介者對象和同事對象的關係通常變成雙向的一對一,這會讓對象的關係更容易理解和實現。

最後我們給出中介者模式的缺點:

  • 當同事類太多時,中介者的職責將很大,它會變得複雜而龐大,以至於系統難以維護。

迭代器模式

下麵我們來介紹迭代器模式

迭代器模式簡述

首先我們來簡單介紹一下迭代器模式:

  • 提供一個對象來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。

迭代器模式結構

迭代器模式主要包含以下角色:

  • 抽象聚合(Aggregate)角色:定義存儲、添加、刪除聚合元素以及創建迭代器對象的介面。

  • 具體聚合(ConcreteAggregate)角色:實現抽象聚合類,返回一個具體迭代器的實例。

  • 抽象迭代器(Iterator)角色:定義訪問和遍歷聚合元素的介面,通常包含 hasNext()、next() 等方法。

  • 具體迭代器(Concretelterator)角色:實現抽象迭代器介面中所定義的方法,完成對聚合對象的遍歷,記錄遍歷的當前位置。

迭代器模式案例

我們通過一個簡單的案例來介紹迭代器:

具體分析:

/*

【例】定義一個可以存儲學生對象的容器對象,將遍歷該容器的功能交由迭代器實現

Student:學生實體類

StudentIterator:抽象迭代器,聲明hasNext、next方法

StudentIteratorImpl:具體迭代器,重寫所有的抽象方法

StudentAggregate:抽象容器類,包含添加元素,刪除元素,獲取迭代器對象的方法

StudentAggregateImpl:具體的容器類,重寫所有的方法

*/

/* 代碼展示 */

// 抽象迭代器
public interface StudentIterator {
    boolean hasNext();
    Student next();
}

// 具體迭代器
public class StudentIteratorImpl implements StudentIterator {
    private List<Student> list;
    private int position = 0;

    public StudentIteratorImpl(List<Student> list) {
        this.list = list;
    }

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

    @Override
    public Student next() {
        Student currentStudent = list.get(position);
        position ++;
        return currentStudent;
    }
}

// 抽象容器類
public interface StudentAggregate {
    void addStudent(Student student);

    void removeStudent(Student student);

    StudentIterator getStudentIterator();
}

// 具體容器類(僅僅只是一個容器,用於生成一個統一的迭代器介面,實際內部還是調用我們上面定義的抽象迭代器的子類迭代器)
public class StudentAggregateImpl implements StudentAggregate {

    private List<Student> list = new ArrayList<Student>();  // 學生列表

    @Override
    public void addStudent(Student student) {
        this.list.add(student);
    }

    @Override
    public void removeStudent(Student student) {
        this.list.remove(student);
    }

    @Override
    public StudentIterator getStudentIterator() {
        return new StudentIteratorImpl(list);
    }
}

迭代器模式分析

首先我們給出迭代器模式的適用場景:

  • 當需要為聚合對象提供多種遍歷方式時。
  • 當需要為遍歷不同的聚合結構提供一個統一的介面時。
  • 當訪問一個聚合對象的內容而無須暴露其內部細節的表示時。

然後我們給出迭代器模式的優點:

  • 它支持以不同的方式遍歷一個聚合對象,在同一個聚合對象上可以定義多種遍歷方式。在迭代器模式中只需要用一個不同的迭代器來替換原有迭代器即可改變遍歷演算法,我們也可以自己定義迭代器的子類以支持新的遍歷方式。
  • 迭代器簡化了聚合類。由於引入了迭代器,在原有的聚合對象中不需要再自行提供數據遍歷等方法,這樣可以簡化聚合類的設計。
  • 在迭代器模式中,由於引入了抽象層,增加新的聚合類和迭代器類都很方便,無須修改原有代碼,滿足 “開閉原則” 的要求。

最後我們給出迭代器模式的缺點:

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

訪問者模式

下麵我們來介紹訪問者模式

訪問者模式簡述

首先我們來簡單介紹一下訪問者模式:

  • 封裝一些作用於某種數據結構中的各元素的操作,它可以在不改變這個數據結構的前提下定義作用於這些元素的新的操作。

訪問者模式結構

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

  • 抽象訪問者(Visitor)角色:定義了對每一個元素(Element)訪問的行為,它的參數就是可以訪問的元素,它的方法個數理論上來講與元素類個數(Element的實現類個數)是一樣的,從這點不難看出,訪問者模式要求元素類的個數不能改變。
  • 具體訪問者(ConcreteVisitor)角色:給出對每一個元素類訪問時所產生的具體行為。
  • 抽象元素(Element)角色:定義了一個接受訪問者的方法(accept),其意義是指,每一個元素都要可以被訪問者訪問。
  • 具體元素(ConcreteElement)角色: 提供接受訪問方法的具體實現,而這個具體的實現,通常情況下是使用訪問者提供的訪問該元素類的方法。
  • 對象結構(Object Structure)角色:定義當中所提到的對象結構,對象結構是一個抽象表述,具體點可以理解為一個具有容器性質或者複合對象特性的類,它會含有一組元素(Element),並且可以迭代這些元素,供訪問者訪問。

訪問者模式案例

我們給出一個簡單的案例來解釋訪問者模式:

具體分析:

/*

【例】給寵物喂食

現在養寵物的人特別多,我們就以這個為例,當然寵物還分為狗,貓等,要給寵物喂食的話,主人可以喂,其他人也可以喂食。

- 訪問者角色:給寵物喂食的人
- 具體訪問者角色:主人、其他人
- 抽象元素角色:動物抽象類
- 具體元素角色:寵物狗、寵物貓
- 結構對象角色:主人家

*/

/* 代碼展示 */

// 抽象訪問者
public interface Person {
    void feed(Cat cat);

    void feed(Dog dog);
}

// 具體訪問者(Owner主人,Someone其他人)
public class Owner implements Person {

    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂食貓");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂食狗");
    }
}

public class Someone implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("其他人喂食貓");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("其他人喂食狗");
    }
}

// 抽象節點
public interface Animal {
    void accept(Person person);
}

// 具體節點
public class Dog implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,汪汪汪!!!");
    }
}

public class Cat implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,喵喵喵!!!");
    }
}

// 對象結構
public class Home {
    
    // 需要操作的節點
    private List<Animal> nodeList = new ArrayList<Animal>();

    // 進行操作節點的訪問者
    public void action(Person person) {
        for (Animal node : nodeList) {
            // 訪問者觸動了節點,以節點的形式調用方法
            node.accept(person);
        }
    }

    //添加操作
    public void add(Animal animal) {
        nodeList.add(animal);
    }
}

// 測試類
public class Client {
    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Dog());
        home.add(new Cat());

        Owner owner = new Owner();
        home.action(owner);

        Someone someone = new Someone();
        home.action(someone);
    }
}

訪問者模式分析

首先我們給出訪問者模式的適用場景:

  • 對象結構相對穩定,但其操作演算法經常變化的程式。
  • 對象結構中的對象需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響對象的結構。

然後我們給出訪問者模式的優點:

  • 擴展性好

    在不修改對象結構中的元素的情況下,為對象結構中的元素添加新的功能。

  • 復用性好

    通過訪問者來定義整個對象結構通用的功能,從而提高復用程度。

  • 分離無關行為

    通過訪問者來分離無關的行為,把相關的行為封裝在一起,構成一個訪問者,這樣每一個訪問者的功能都比較單一。

最後我們給出訪問者模式的缺點:

  • 對象結構變化很困難

    在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”。

  • 違反了依賴倒置原則

    訪問者模式依賴了具體類,而沒有依賴抽象類。

備忘錄模式

下麵我們來介紹備忘錄模式

備忘錄模式簡述

我們首先給出備忘錄模式的概念:

  • 快照模式,在不破壞封裝性的前提下,捕獲一個對象的內部狀態,併在該對象之外保存這個狀態,以便以後當需要時能將該對象恢復到原先保存的狀態。

備忘錄模式結構

備忘錄模式的主要角色如下:

  • 發起人(Originator)角色:記錄當前時刻的內部狀態信息,提供創建備忘錄和恢復備忘錄數據的功能,實現其他業務功能,它可以訪問備忘錄里的所有信息。
  • 備忘錄(Memento)角色:負責存儲發起人的內部狀態,在需要的時候提供這些內部狀態給發起人。
  • 管理者(Caretaker)角色:對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內容進行訪問與修改。

備忘錄有兩個等效的介面:

  • 窄介面:管理者(Caretaker)對象(和其他發起人對象之外的任何對象)看到的是備忘錄的窄介面(narror Interface),這個窄介面只允許他把備忘錄對象傳給其他的對象。
  • 寬介面:與管理者看到的窄介面相反,發起人對象可以看到一個寬介面(wide Interface),這個寬介面允許它讀取所有的數據,以便根據這些數據恢復這個發起人對象的內部狀態。

白箱備忘錄模式

我們通過一個案例來介紹白箱備忘錄:

/*

【例】游戲挑戰BOSS

游戲中的某個場景,一游戲角色有生命力、攻擊力、防禦力等數據,在打Boss前和後一定會不一樣的,我們允許玩家如果感覺與Boss決鬥的效果不理想可以讓游戲恢復到決鬥之前的狀態。

【白箱備忘錄】

備忘錄角色對任何對象都提供一個介面,即寬介面,備忘錄角色的內部所存儲的狀態就對所有對象公開。

由於備忘錄角色對任何對象都提供寬介面(所有許可權),所以實際上是不安全的

*/

/* 代碼展示 */

// 游戲角色類(發起人)
public class GameRole {
    private int vit; //生命力
    private int atk; //攻擊力
    private int def; //防禦力

    //初始化狀態
    public void initState() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }

    //戰鬥
    public void fight() {
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }

    // 保存角色狀態(創建出一個備忘錄,該備忘錄記錄當前狀況)
    public RoleStateMemento saveState() {
        return new RoleStateMemento(vit, atk, def);
    }

    // 回覆角色狀態(需要一個備忘錄,記錄需要恢復的狀態)
    public void recoverState(RoleStateMemento roleStateMemento) {
        this.vit = roleStateMemento.getVit();
        this.atk = roleStateMemento.getAtk();
        this.d

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 某一日晚上上線,測試同學在回歸項目黃金流程時,有一個工單項目介面報JSF序列化錯誤,馬上升級對應的client包版本,編譯部署後錯誤消失。 線上問題是解決了,但是作為程式員要瞭解問題發生的原因和本質。但這都是為什麼呢? ...
  • 前言 本文寫給想學C#的朋友,目的是以儘快的速度入門 C#好學嗎? 對於這個問題,我以前的回答是:好學!但仔細想想,不是這麼回事,對於新手來說,C#沒有那麼好學。 反而學Java還要容易一些,學Java Web就行了,就是SpringBoot那一套。 但是C#方向比較多,你是學控制台程式、WebAP ...
  • 前言 當別人做大數據用Java、Python的時候,我使用.NET做大數據、數據挖掘,這確實是值得一說的事。 寫的並不全面,但都是實際工作中的內容。 .NET在大數據項目中,可以做什麼? 寫腳本(使用控制台程式+頂級語句) 寫工具(使用Winform) 寫介面、寫服務 使用C#寫代碼的優點是什麼? ...
  • 新建一個STM32CubeIDE 新工程 選擇自己的晶元型號,我的是STM32F103RCT6 選擇工程保存位置,不能有中文路徑,會報錯 選擇下載方式、一定要選,不然下次下載有問題 選擇時鐘來源,我的板子有8Mhz、32.768Khz兩個外置晶振 配置時鐘頻率 生成相應的.c .h文件,方便我們管理 ...
  • Jenkins 與 keycloak集成 搭建keycloak 運行keycloak服務 創建docker-compose.yaml文件,運行docker-compose up -d 拉起服務 version: '3' networks: keynet: driver: bridge service ...
  • 在上篇文章 《深入理解 Linux 物理記憶體分配全鏈路實現》 中,筆者為大家詳細介紹了 Linux 記憶體分配在內核中的整個鏈路實現: 但是當內核執行到 get_page_from_freelist 函數,準備進入伙伴系統執行具體記憶體分配動作的相關邏輯,筆者考慮到文章篇幅的原因,並沒有過多的著墨,算是 ...
  • 總結-舊生命周期 初始化階段: 由ReactDOM.render()觸發 初次渲染 constructor() componentWillMount() render() componentDidMount() > 常用 一般在這個鉤子中做一些初始化的事,例如:開啟定時器,發送網路請求,訂閱消息 更 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 本文用一個簡單的 demo 講解 App端 半屏連續掃碼 的實現方式,包括(條形碼、二維碼等各種各樣的碼)。 我會從實現思路講起,如果你比較急可以直接跳到 動手實現 章節獲取代碼。 開發和運行環境 開發工具:HBuilderX 前端框架: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...