狀態模式 | State Pattern

Posted by Ian Tsai on Saturday, October 3, 2020

本系列文章同步分享於IT邦幫忙第12屆鐵人賽


Design Pattern 系列文章導讀


Design Pattern可以說是開發上大家都會遇到的一個課題, 這系列文會從Design Principles、各種design pattern到最後的Anti-Patterns & Code Smells介紹下去,讓我們可以更了解各種pattern的使用時機與場合。 預計目標主題如下,若有哪部分不熟的章節可以直接點進去看
註:可以利用Online Java Compiler IDE

  1. 設計模式 - 入門篇
  2. 設計模式 - 原則篇 | Design Principles
  3. 設計模式 - 創建型模型篇 | Creational Patterns
  4. 設計模式 - 結構型模型篇 | Structural Patterns
  5. 設計模式 - 行為型模型篇 | Behavioural Patterns
  6. 設計模式 - 番外篇
  • 定義


有狀態的物件,把複雜的邏輯判斷分配到不同的狀態物件中,允許狀態物件在其內部狀態發生改變時改變行為。

比如說,人在開心的時候會開懷大笑,在傷心的時候會哭泣,在生氣的時候會想打人…等等,根據不同的狀態,會有不同的行為。而在系統中,通常這個行為會用if-else去做判斷,對應不同的情況去做對應的處理。但如果物件的狀態很多,程式碼會變得很複雜,而且當要新增狀態時,要更改if-else的邏輯,這樣就違背了開閉原則,不利於擴充。

而狀態模式的解決方式就是,控制一個物件狀態的條件過於複雜時,把相關判斷娜及取出,放到一系列的狀態類別中,就可以簡化原本複雜的邏輯。

  • State Pattern 成員


成員 功用
Context(環境) 定義Client的接口,維護目前的狀態,並將狀態的操作委託給目前狀態物件來處理。
State(狀態) 定義一個接口,用來封裝Context中特定狀態所對應的行為。
ConcreteState(實體狀態) 實現State所定義的方法。
  • State Pattern 實作


可以試想,今天到銀行要辦一張信用卡,申辦的步驟非常的煩瑣如下:

填寫資料後
1. 業務審核:資料是否完整。
2. 行員審核:查看帳戶及信用卡費是否有按時繳交。
3. 信用卡部審核:調閱聯徵紀錄查詢分數。

以上是簡略的步驟,分成ABC三階段。接著就來實作申請信用卡時的步驟。

首先先將抽象狀態以及實體狀態建立起來,在每個不同的狀態中,會有不同的邏輯實作。


//抽象狀態
abstract class State {
    public abstract boolean Handle(Context context, boolean status);
}
//實體狀態A:業務審核
class ConcreteStateA extends State {
    public boolean Handle(Context context, boolean status) {
        if (status){
            System.out.println("資料填寫完整,業務審核通過,準備進入行員審核。");
            context.setState(new ConcreteStateB());
        } else {
            System.out.println("業務審核中。");
        }
        return false;
    }
}
//實體狀態B:行員審核
class ConcreteStateB extends State {
    public boolean Handle(Context context, boolean status) {
        if (status){
            System.out.println("行員審核通過,準備進入信用卡部審核。");
            context.setState(new ConcreteStateC());
        } else {
            System.out.println("行員審核中。");
        }
        return false;
    }
}
//實體狀態C:信用卡部審核
class ConcreteStateC extends State {
    public boolean Handle(Context context, boolean status) {
        if (status){
            System.out.println("信用卡部審核通過,寄送信用卡。");
            return true;
        } else {
            System.out.println("信用卡部審核中。");
            return false;
        }
    }
}

接著建立Context環境類,要在內部定義初始狀態,這個範例的初始狀態是業務審核ConcreteStateA。

//環境類
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 boolean Handle(boolean status) {
        return state.Handle(this, status);
    }
}

最後在Client來使用看看


import java.util.*;
public class StatePatternClient{
    public static void main(String[] args) {       
        Context context = new Context();    //建立環境
        
        Scanner scanner = new Scanner(System.in);
        boolean end = false;
        System.out.println("申請已送出,請等待審核結果。");
        while (!end){
            System.out.println("請輸入選項(1:審核通過;2:繼續審核;3:結束程式):");
            int num = scanner.nextInt();
            if (num == 1){
                end = context.Handle(true);    //處理請求
            } else if (num == 2){
                end = context.Handle(false);    //處理請求
            } else if (num == 3){
                end = true;
            } else {
                System.out.println("請輸入正確指令" );
            }
        }
    }
}

  • 小結


State Pattern的目標

有狀態的物件,把複雜的邏輯判斷分配到不同的狀態物件中,允許狀態物件在其內部狀態發生改變時改變行為。

State Pattern的成員
Context:定義Client的接口,維護目前的狀態,並將狀態的操作委託給目前狀態物件來處理。
State:定義一個接口,用來封裝Context中特定狀態所對應的行為。
ConcreteState:實現State所定義的方法。
State Pattern的優缺點
優點
1. 減少物件之間的依賴性。
2. 利於系統的擴中。
3. 定義新的子類別可以很容易的新增新狀態。
缺點
1. 增加系統的類別及物件的個數。
2. 結構與實作都相對複雜。
State Pattern的使用時機
1. 物件的行為取決於他的狀態,並必須要執行時根據狀態改變他的行為時。
2. 一個操作具有龐大的分支結構,且這些分支決定物件的狀態時。
  • 範例程式碼


範例:State Pattern 實作

  • References


  • 状态模式(详解版)