設(shè)計(jì)模式之狀態(tài)模式(state模式)

面向?qū)ο缶幊讨校愑脕肀硎緦?duì)象,一般情況下,我們需要考慮用類來表示什么具體的東西。類對(duì)應(yīng)的東西可能存在于真實(shí)世界中,也可能不存在于真實(shí)世界中。
狀態(tài)模式所表示的類,一般就不存在真實(shí)世界的某個(gè)東西,因?yàn)闋顟B(tài)模式中的類是用來表示狀態(tài)的。狀態(tài)一般都是抽象的,所以往往沒有具體對(duì)應(yīng)于真實(shí)世界的對(duì)象。
我們用類來表示狀態(tài),那么不同的狀態(tài)就用不同的類來表示,我們只要通過切換不同的類就可以切換不同的狀態(tài)。

狀態(tài)模式的具體實(shí)例

我們考慮設(shè)計(jì)一個(gè)金庫警報(bào)系統(tǒng),這個(gè)系統(tǒng)會(huì)根據(jù)白天晚上做出不同的響應(yīng)。

有一個(gè)金庫
金庫與警報(bào)中心相連
金庫里有警鈴和電話
金庫里有時(shí)鐘

金庫只能在白天使用
白天使用金庫,會(huì)在警報(bào)中心留下記錄
晚上使用金庫,會(huì)向警報(bào)中心發(fā)送緊急事態(tài)通知

警鈴白天晚上都能用
使用警鈴,會(huì)向警報(bào)中心發(fā)送緊急事態(tài)通知

電話都可以使用
白天使用電話,會(huì)呼叫警報(bào)中心
晚上使用電話,會(huì)呼叫警報(bào)中心的留言電話

基本就是以上的需求邏輯。

如果我們不使用狀態(tài)模式
那就是大概偽碼如下:

使用金庫調(diào)用的方法() {
  if(白天) {
} else if(晚上) {
}
}

正常通話時(shí)() {
if(白天) {}
else if(晚上) {
}
}

顯然這樣可以實(shí)現(xiàn),也并沒有什么錯(cuò)誤。

但是狀態(tài)模式確實(shí)從不同的角度來考慮問題。

狀態(tài)模式會(huì)發(fā)現(xiàn),這些不同的行為,主要依賴于兩個(gè)狀態(tài),就是白天和晚上。所以狀態(tài)模式會(huì)抽象出這兩種狀態(tài),每個(gè)狀態(tài)就會(huì)有自己的行為實(shí)現(xiàn),比如白天這個(gè)狀態(tài)會(huì)實(shí)現(xiàn)自己的使用金庫的方法,通話的方法,晚上的類也會(huì)實(shí)現(xiàn)自己的行為邏輯,最后我們只要取得狀態(tài)對(duì)象的委托調(diào)用他們的方法就行了,不管他們具體是怎么實(shí)現(xiàn)的。
我們看一下使用狀態(tài)模式的偽碼:

白天的狀態(tài)類 {
      使用金庫的方法
      使用警鈴的方法
     通話的方法
}


晚上的狀態(tài)類 {
      使用金庫的方法
      使用警鈴的方法
     通話的方法
}

我們看到普通方法和狀態(tài)模式的區(qū)別就是狀態(tài)模式中,定義了狀態(tài)類,就不需要if語句來判斷了。所以當(dāng)我們遇到很多個(gè)ifelse語句的時(shí)候,往往可以考慮狀態(tài)模式,阿里最新的java開發(fā)手冊里面就有一條相關(guān)的推薦

image.png

下面我們就來具體實(shí)現(xiàn)代碼

定義狀態(tài)類的接口:

package State;

public interface State {
    public abstract void doClock(Context context, int hour);    // 設(shè)置時(shí)間
    public abstract void doUse(Context context);                // 使用金庫
    public abstract void doAlarm(Context context);              // 按下警鈴
    public abstract void doPhone(Context context);              // 正常通話
}

實(shí)現(xiàn)兩個(gè)具體的白天和晚上的狀態(tài)類

package State;

public class DayState implements State {
    private static DayState singleton = new DayState();
    private DayState() {                                // 構(gòu)造函數(shù)的可見性是private
    }
    public static State getInstance() {                 // 獲取唯一實(shí)例
        return singleton;
    }
    public void doClock(Context context, int hour) {    // 設(shè)置時(shí)間
        if (hour < 9 || 17 <= hour) {
            context.changeState(NightState.getInstance());
        }
    }
    public void doUse(Context context) {                // 使用金庫
        context.recordLog("使用金庫(白天)");
    }
    public void doAlarm(Context context) {              // 按下警鈴
        context.callSecurityCenter("按下警鈴(白天)");
    }
    public void doPhone(Context context) {              // 正常通話
        context.callSecurityCenter("正常通話(白天)");
    }
    public String toString() {                          // 顯示表示類的文字
        return "[白天]";
    }
}

package State;

public class NightState implements State {
    private static NightState singleton = new NightState();
    private NightState() {                              // 構(gòu)造函數(shù)的可見性是private
    }
    public static State getInstance() {                 // 獲取唯一實(shí)例
        return singleton;
    }
    public void doClock(Context context, int hour) {    // 設(shè)置時(shí)間
        if (9 <= hour && hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }
    public void doUse(Context context) {                // 使用金庫
        context.callSecurityCenter("緊急:晚上使用金庫!");
    }
    public void doAlarm(Context context) {              // 按下警鈴
        context.callSecurityCenter("按下警鈴(晚上)");
    }
    public void doPhone(Context context) {              // 正常通話
        context.recordLog("晚上的通話錄音");
    }
    public String toString() {                          // 顯示表示類的文字
        return "[晚上]";
    }
}

Context類是負(fù)責(zé)管理狀態(tài)和聯(lián)系警報(bào)中心的接口,它定義了基本的警報(bào)中心的行為,

package State;

public interface Context {

    public abstract void setClock(int hour);                // 設(shè)置時(shí)間
    public abstract void changeState(State state);          // 改變狀態(tài)
    public abstract void callSecurityCenter(String msg);    // 聯(lián)系警報(bào)中心
    public abstract void recordLog(String msg);             // 在警報(bào)中心留下記錄
}

safeframe類實(shí)現(xiàn)了context接口

package State;

import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.Button;
import java.awt.TextField;
import java.awt.TextArea;
import java.awt.Panel;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class SafeFrame extends Frame implements ActionListener, Context {
    private TextField textClock = new TextField(60);        // 顯示當(dāng)前時(shí)間
    private TextArea textScreen = new TextArea(10, 60);     // 顯示警報(bào)中心的記錄
    private Button buttonUse = new Button("使用金庫");      // 金庫使用按鈕
    private Button buttonAlarm = new Button("按下警鈴");    // 按下警鈴按鈕
    private Button buttonPhone = new Button("正常通話");    // 正常通話按鈕
    private Button buttonExit = new Button("結(jié)束");         // 結(jié)束按鈕

    private State state = DayState.getInstance();           // 當(dāng)前的狀態(tài)

    // 構(gòu)造函數(shù)
    public SafeFrame(String title) {
        super(title);
        setBackground(Color.lightGray);
        setLayout(new BorderLayout());
        //  配置textClock
        add(textClock, BorderLayout.NORTH);
        textClock.setEditable(false);
        // 配置textScreen
        add(textScreen, BorderLayout.CENTER);
        textScreen.setEditable(false);
        // 為界面添加按鈕
        Panel panel = new Panel();
        panel.add(buttonUse);
        panel.add(buttonAlarm);
        panel.add(buttonPhone);
        panel.add(buttonExit);
        // 配置界面
        add(panel, BorderLayout.SOUTH);
        // 顯示
        pack();
        show();
        // 設(shè)置監(jiān)聽器
        buttonUse.addActionListener(this);
        buttonAlarm.addActionListener(this);
        buttonPhone.addActionListener(this);
        buttonExit.addActionListener(this);
    }
    // 按鈕被按下后該方法會(huì)被調(diào)用
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        if (e.getSource() == buttonUse) {           // 金庫使用按鈕
            state.doUse(this);
        } else if (e.getSource() == buttonAlarm) {  // 按下警鈴按鈕
            state.doAlarm(this);
        } else if (e.getSource() == buttonPhone) {  // 正常通話按鈕
            state.doPhone(this);
        } else if (e.getSource() == buttonExit) {   // 結(jié)束按鈕
            System.exit(0);
        } else {
            System.out.println("?");
        }
    }
    // 設(shè)置時(shí)間
    public void setClock(int hour) {
        String clockstring = "現(xiàn)在時(shí)間是";
        if (hour < 10) {
            clockstring += "0" + hour + ":00";
        } else {
            clockstring += hour + ":00";
        }
        System.out.println(clockstring);
        textClock.setText(clockstring);
        state.doClock(this, hour);
    }
    // 改變狀態(tài)
    public void changeState(State state) {
        System.out.println("從" + this.state + "狀態(tài)變?yōu)榱? + state + "狀態(tài)。");
        this.state = state;
    }
    // 聯(lián)系警報(bào)中心
    public void callSecurityCenter(String msg) {
        textScreen.append("call! " + msg + "\n");
    }
    // 在警報(bào)中心留下記錄
    public void recordLog(String msg) {
        textScreen.append("record ... " + msg + "\n");
    }
}

main類啟動(dòng)

package State;

public class Main {
    public static void main(String[] args) {
        SafeFrame frame = new SafeFrame("State Sample");
        while (true) {
            for (int hour = 0; hour < 24; hour++) {
                frame.setClock(hour);   // 設(shè)置時(shí)間
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
        }
    }
}

image.png

狀態(tài)模式的分析

狀態(tài)模式的角色:

  • state狀態(tài)
    表示狀態(tài),定義了根據(jù)不同狀態(tài)進(jìn)行不同處理的接口,該接口是那些處理內(nèi)容依賴于狀態(tài)的方法集合,對(duì)應(yīng)實(shí)例的state類

  • 具體的狀態(tài)
    實(shí)現(xiàn)了state接口,對(duì)應(yīng)daystate和nightstate

  • context
    context持有當(dāng)前狀態(tài)的具體狀態(tài)的實(shí)例,此外,他還定義了供外部調(diào)用者使用的狀態(tài)模式的接口。

狀態(tài)模式的類圖:

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • Iterator模式 (迭代器) 一個(gè)一個(gè)遍歷 一個(gè)集合類可以遵守 Iterator 協(xié)議,并實(shí)現(xiàn)一個(gè) Itera...
    SSBun閱讀 1,994評(píng)論 0 15
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,093評(píng)論 1 15
  • 1. 2007年發(fā)生了兩件大事: 陳浩掉井里了。 陳浩談戀愛了。 2. 蘇安琳對(duì)陳浩死纏爛打了兩個(gè)月后的某一天,陳...
    天蝎三兒閱讀 391評(píng)論 4 3
  • 每個(gè)人的家都不近乎完美。有些人會(huì)覺得自家的臥室設(shè)計(jì)不好,或者玄關(guān)設(shè)計(jì)欠缺細(xì)節(jié)等等。鑒于此,今天小編就為大家推薦幾款...
    b6e3f9f81958閱讀 538評(píng)論 0 0

友情鏈接更多精彩內(nèi)容