23種設計模式(七)-狀態設計模式

一. 什麼是狀態模式?

狀態模式(State Pattern):它主要用來解決對象在多種狀態轉換時,需要對外輸出不同的行為的問題。狀態和行為是一一對應的,狀態之間可以相互轉換。當一個對象的內在狀態改變時,允許改變其行為,這個對象看起來像是改變了其類。例如:淘寶下單,訂單有待付款,已付款待發貨,待收貨,待評價, 已完成等狀態。每個狀態對應的行為都是不同的, 一個狀態完成會流轉到下一個狀態。

通常對有狀態的對象進行編程,我們的解決方案是:思考可能存在的所有狀態,然後使用 if-else 或 switch-case 語句來進行狀態判斷,然後再根據不同的狀態進行不同的處理。如上面的案例–淘寶下單:

public class ClientApplication {
    public static void main(String[] args) {
        String status = "待付款";
        if ("待付款".equals(status)) {
            // 執行付款邏輯
        } else if("待發貨".equals(status)) {
            // 執行發貨邏輯
        } else if("待收貨".equals(status)) {
            // 執行收貨邏輯
        } else if("待評價".equals(status)) {
            // 執行評價邏輯
        } else if("待收貨".equals(status)) {
            // 執行收穫邏輯
        }
    }
}

大量的if…else的缺點很明顯

  1. 違背開閉原則: 當增加一種狀態的時候, 需要修改原來的邏輯
  2. 當狀態很多的時候, 代碼段很長, 臃腫, 不容易維護, 可擴展性差.

狀態模式可以很好地解決這個問題。
狀態模式的思想:當是條件語句表示一個對象狀態轉換過於複雜時,可以把條件判斷中的「判斷邏輯」提取出來,放在單獨的類中,當前上下文處於那種狀態,直接用相應的狀態類對象進行處理,這樣的好處是:能把原來複雜的判斷邏輯簡單化,消除了 if-else、switch-case 等條件判斷語句,代碼更有層次性,且具備良好的擴展力, 可維護性。

二. 狀態模式的結構

狀態模式把因環境改變而變化的對象行為包裝在不同的狀態類中,其目的保證對象的狀態變化時, 其行為也隨之改變。接下來, 我們來看看狀態模式的結構。

從中我們可以看出有四個組成部分:

1. 環境類

環境類也是上下文類, 狀態的變化依賴於環境, 環境記錄當前的狀態. 環境類擁有多種狀態的對象. 環境類的狀態存在多樣性, 不同狀態下對象的行為有所不同, 因此將狀態獨立出去形成單獨的狀態類。

> 1) 在環境類中維護一個抽象狀態State的實例, 這個實例存儲當前狀態。
> 2) 在環境類中定義所有狀態執行的方法.

package com.lxl.www.designPatterns.statePattern.context;

/**
 * 上下文環境類
 */
public class Context {
    // 在環境類中維護一個抽象狀態State的實例, 這個實例存儲當前狀態。
    private State state;
    public Context() {
        this.state = new ConcreteStateA(this);
    }

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

    /**
     * 在環境類中定義所有狀態執行的方法.
     */
    public void handler1() {

        this.state.handler1();
    }

    public void handler2() {
        this.state.handler2();
    }

}


2. State抽象狀態類

  抽象環境中聲明一個環境角色,提供各個狀態類自行訪問,並且提供所有狀態的抽象行為,由各個實現類實現。

/**
 * 狀態抽象類
 */
public abstract class State {

    /**
     * 環境上下文
     */
    public Context context;
    public State(Context context) {
        this.context = context;
    }

    /**
     * 定義了所有狀態的抽象方法
     */
    abstract void handler1();
    abstract void handler2();
}

3. 具體狀態

  具體狀態實現,這裡以定義ConcreteStateA、ConcreteStateB、ConcreteStateC三個具體狀態類。具體狀態類繼承自State抽象類, 並實現抽象方法。

package com.lxl.www.designPatterns.statePattern.context;

public class ConcreteStateA extends State{

    public ConcreteStateA(Context context) {
        super(context);
    }

    @Override
    void handler1() {
        System.out.println("執行ConcreteStateA中handler1的邏輯");
        this.context.setState(new ConcreteStateB(context));
    }

    @Override
    void handler2() {
        System.out.println("執行ConcreteStateA中handler2的邏輯");
        this.context.setState(new ConcreteStateC(context));
    }
}

在這裡, 我們重寫了狀態類的狀態方法。當執行完方法一的業務邏輯後, 將狀態變更為 ConcreteStateB. 執行完handler2的業務邏輯後, 將狀態變更為ConcreteStateC。
這種自動變更狀態, 在調用方是無感知的。

4. Client客戶端

  構建Context環境上下文類的實例對象,初始化時設置初始狀態是ConcreteStateA,執行行為觀察結果。

package com.lxl.www.designPatterns.statePattern.context;

public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.handler1();
        context.handler1();
        context.handler1();
        context.handler2();
    }
}

全部源碼:

package com.lxl.www.designPatterns.statePattern.context;

/**
 * 上下文環境類
 */
public class Context {
    // 在環境類中維護一個抽象狀態State的實例, 這個實例存儲當前狀態。
    private State state;
    public Context() {
        this.state = new ConcreteStateA(this);
    }

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

    /**
     * 在環境類中定義所有狀態執行的方法.
     */
    public void handler1() {

        this.state.handler1();
    }

    public void handler2() {
        this.state.handler2();
    }

}



/**
 * 狀態抽象類
 */
public abstract class State {

    /**
     * 環境上下文
     */
    public Context context;
    public State(Context context) {
        this.context = context;
    }

    /**
     * 定義了所有狀態的抽象方法
     */
    abstract void handler1();
    abstract void handler2();
}



public class ConcreteStateA extends State{

    public ConcreteStateA(Context context) {
        super(context);
    }

    @Override
    void handler1() {
        System.out.println("執行ConcreteStateA中handler1的邏輯");
        this.context.setState(new ConcreteStateB(context));
    }

    @Override
    void handler2() {
        System.out.println("執行ConcreteStateA中handler2的邏輯");
        this.context.setState(new ConcreteStateC(context));
    }
}

public class ConcreteStateB extends State{
    public ConcreteStateB(Context context) {
        super(context);
    }

    @Override
    void handler1() {
        System.out.println("執行ConcreteStateB中handler1的邏輯");
        this.context.setState(new ConcreteStateC(context));
    }

    @Override
    void handler2() {
        System.out.println("執行ConcreteStateB中handler2的邏輯");
        this.context.setState(new ConcreteStateA(context));
    }
}



public class ConcreteStateC extends State{
    public ConcreteStateC(Context context) {
        super(context);
    }

    @Override
    void handler1() {
        System.out.println("執行ConcreteStateC中handler1的邏輯");
        this.context.setState(new ConcreteStateA(context));
    }

    @Override
    void handler2() {
        System.out.println("執行ConcreteStateC中handler2的邏輯");
        this.context.setState(new ConcreteStateB(context));
    }
}



public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.handler1();
        context.handler1();
        context.handler1();
        context.handler1();
    }
}

客戶端直接結果分析:

  1. Context實例花的時候, 狀態是 ConcreteStateA
  2. 第一次執行context.handler1();將狀態切換到了ConcreteStateB
  3. 第二次執行context.handler1();將狀態切換到了ConcreteStateC
  4. 第三次執行context.handler1();將狀態切換到了ConcreteStateA

所以, 最後的運行結果是:

執行ConcreteStateA中handler1的邏輯

執行ConcreteStateB中handler1的邏輯

執行ConcreteStateC中handler1的邏輯

執行ConcreteStateA中handler1的邏輯

三. 狀態模式的應用實例

下面以商品下單為例,來看看狀態模式的應用。

在電商系統購物的過程中,訂單一般有這幾種狀態,待付款,待出庫,待收貨, 待評價,完成。不同的狀態下,用戶看到的行為是不同的。

我們由淺入深, 來看看幾種狀態模式的實現。

第一種: 狀態是一條線的實現邏輯

先來看看這種方法的流程:

這種狀態的特點是: 上一個狀態流轉到下一個狀態, 下一個流轉到下下個,以此類推。直到最後一個沒有狀態流轉了。來看看源碼:

第一步:定義上下文環境類。

用orderStatus來記錄當前的狀態。並定義了所有狀態的方法。

package com.lxl.www.designPatterns.statePattern.order;

/**
 * 訂單上下文
 */
public class OrderContext {

    /**在上下文類中記錄訂單狀態*/
    private IOrderStatus orderStatus;

    public OrderContext() {
        // 最開始是待付款狀態
        this.orderStatus = new Pending();
    }

    public void setOrderStatus(IOrderStatus orderStatus) {
        this.orderStatus = orderStatus;
    }

    /**
     * 業務邏輯操作
     */
    public void businessHandler() {
        this.orderStatus.businessHandler(this);
    }

    /**
     * 打印當前業務
     */
    public void printInfo() {
        this.orderStatus.printInfo();
    }
}

第二步: 定義狀態抽象類。

裏面定義所有狀態類要實現的方法。

package com.lxl.www.designPatterns.statePattern.order;

/**
 * 訂單狀態
 */
public interface IOrderStatus {
    void businessHandler(OrderContext context);

    void printInfo();
}

這裏面就有兩個方法, 一個是狀態要執行的業務邏輯, 另一個是打印當前狀態。

第三步:定義狀態具體類。

這裡定義了5個具體類,分別是:待付款,待出庫,待收貨, 待評價,完成

package com.lxl.www.designPatterns.statePattern.order;
/**
 * 待付款
 */
public class Pending implements IOrderStatus{
    @Override
    public void businessHandler(OrderContext context) {
        //執行業務, 完成付款, 進入到下一個狀態
        System.out.println("付款完成");
        context.setOrderStatus(new WaitOut());
    }

    @Override
    public void printInfo() {
        System.out.println("當前狀態等::待付款");
    }
}


package com.lxl.www.designPatterns.statePattern.order;
/**
 * 待出庫
 */
public class WaitOut implements IOrderStatus{
    @Override
    public void businessHandler(OrderContext context) {
        //執行業務, 完成付款, 進入到下一個狀態
        System.out.println("貨物已出庫");
        context.setOrderStatus(new WaitReceive());
    }

    @Override
    public void printInfo() {
        System.out.println("當前狀態等::待出庫");
    }
}


package com.lxl.www.designPatterns.statePattern.order;
/**
 * 待收貨
 */
public class WaitReceive implements IOrderStatus {
    @Override
    public void businessHandler(OrderContext context) {
        //執行業務, 完成付款, 進入到下一個狀態
        System.out.println("已經收貨");
        context.setOrderStatus(new OrderEvaluation());
    }

    @Override
    public void printInfo() {
        System.out.println("當前狀態等::待收貨");
    }
}


package com.lxl.www.designPatterns.statePattern.order;
/**
 * 訂單評價
 */
public class OrderEvaluation implements IOrderStatus{
    @Override
    public void businessHandler(OrderContext context) {
        //執行業務, 完成付款, 進入到下一個狀態
        System.out.println("已經評價");
        context.setOrderStatus(new Finish());
    }

    @Override
    public void printInfo() {
        System.out.println("當前狀態等::待評價");
    }
}


package com.lxl.www.designPatterns.statePattern.order;
/**
 * 訂單完成
 */
public class Finish implements IOrderStatus{
    @Override
    public void businessHandler(OrderContext context) {
        // 執行業務, 完成付款, 進入到下一個狀態
        System.out.println("完成工作處理完畢");
    }

    @Override
    public void printInfo() {
        System.out.println("當前狀態::訂單完成");
    }
}


第四步: 定義客戶端類, 模擬下單的流程

package com.lxl.www.designPatterns.statePattern.order;

public class OrderClient {
    public static void main(String[] args) {
        OrderContext orderContext = new OrderContext();

        // 開始下單
        System.out.println("==========開始下單==========");
        orderContext.printInfo();

        // 付款
        System.out.println("==========付款==========");
        orderContext.businessHandler();
        orderContext.printInfo();

        // 貨物出庫
        System.out.println("==========貨物出庫==========");
        orderContext.businessHandler();
        orderContext.printInfo();

        // 收貨
        System.out.println("==========收貨==========");
        orderContext.businessHandler();
        orderContext.printInfo();

        // 評價訂單
        System.out.println("==========評價訂單==========");
        orderContext.businessHandler();
        orderContext.printInfo();

        // 訂單完成
        System.out.println("==========訂單完成==========");
        orderContext.businessHandler();
    }
}

接下來我們來看看運行效果:

開始下單

當前狀態等::待付款

付款

付款完成

當前狀態等::待出庫

貨物出庫

貨物已出庫

當前狀態等::待收貨

收貨

已經收貨

當前狀態等::待評價

評價訂單

已經評價

當前狀態::訂單完成

訂單完成

完成工作處理完畢

第二種: 帶有取消付款和退貨退款狀態。

退貨退款最開始只能是收到貨以後才能退。

這種方式和第一個有所不同, 待付款和確認收貨不止有一個狀態流轉,當有多個狀態流轉,甚至是更多的狀態是,我們應該如何處理呢?

第一步: 定義上下文環境類。

這裡環境上下文定義了所有的狀態方法。

package com.lxl.www.designPatterns.statePattern.order2;

/**
 * 訂單上下文
 */
public class OrderContext {

    /**在上下文類中記錄訂單狀態*/
    private IOrderStatus orderStatus;

    public OrderContext() {
        System.out.println("開始購物");
        // 最開始是待付款狀態
        this.orderStatus = new Pending();
    }

    public void setOrderStatus(IOrderStatus orderStatus) {
        this.orderStatus = orderStatus;
    }


    /**
     * 付款完成
     */
    public void pending() {
        this.orderStatus.pending(this);
    }

    public void waitOut() {
        this.orderStatus.waitOut(this);
    }

    public void waitReceive() {
        this.orderStatus.waitReceive(this);
    }

    public void confirmReceived() {
        this.orderStatus.confirmReceived(this);
    }

    public void orderEvaluation() {
        this.orderStatus.orderEvaluation(this);
    }

    public void finish() {
        this.orderStatus.finish(this);
    }

    public void cancelPay() {
        this.orderStatus.cancelPay(this);
    }

    public void refunds() {
        this.orderStatus.refunds(this);
    }

}

第二步:定義抽象狀態類

package com.lxl.www.designPatterns.statePattern.order2;

/**
 * 訂單狀態
 */
public interface IOrderStatus {
    /*
     * 待付款
     */
    void pending(OrderContext context);

    /*
     * 取消付款
     */
    void cancelPay(OrderContext context);

    /*
     * 待出庫
     */
    void waitOut(OrderContext context);

    /*
     * 退貨退款
     */
    void refunds(OrderContext context);

    /*
     * 待收貨
     */
    void waitReceive(OrderContext context);

    /*
     * 確認收貨
     */
    void confirmReceived(OrderContext context);

    /*
     * 訂單評價
     */
    void orderEvaluation(OrderContext context);

    /*
     * 訂單完成
     */
    void finish(OrderContext context);

}

第三步:定義具體狀態類。

我們這裡定義了待付款,待出庫,待收貨, 確認收貨,訂單評價,訂單完成,取消付款,退貨退款一共8個狀態。

待付款

package com.lxl.www.designPatterns.statePattern.order2;

/**
 * 待付款
 */
public class Pending implements IOrderStatus {
    public Pending() {
        System.out.println("當前狀態::待付款");

    }

    @Override
    public void pending(OrderContext context) {
        System.out.println("付款完成了, 待出庫");
        context.setOrderStatus(new WaitOut());
    }

    /*
     * 取消付款
     */
    public void cancelPay(OrderContext context) {
        System.out.println("取消付款");
        context.setOrderStatus(new Finish());
    }

    @Override
    public void waitOut(OrderContext context) {
        System.out.println("去付款-->付款完成,待出庫");
        context.setOrderStatus(new WaitOut());
    }

    /*
     * 退貨退款
     */
    public void refunds(OrderContext context) {

    }

    @Override
    public void waitReceive(OrderContext context) {

    }

    @Override
    public void confirmReceived(OrderContext context) {

    }

    @Override
    public void orderEvaluation(OrderContext context) {

    }

    @Override
    public void finish(OrderContext context) {

    }
}

待出庫

package com.lxl.www.designPatterns.statePattern.order2;

/**
 * 待出庫
 */
public class WaitOut implements IOrderStatus {

    public WaitOut() {
        System.out.println("當前狀態::待出庫");
    }

    @Override
    public void pending(OrderContext context) {

    }

    @Override
    public void cancelPay(OrderContext context) {

    }

    /*
     * 退貨退款
     */
    public void refunds(OrderContext context) {
        System.out.println("申請退貨");
        context.setOrderStatus(new Refunds());
    }

    @Override
    public void waitOut(OrderContext context) {
        System.out.println("出庫完成, 待收貨");
        context.setOrderStatus(new WaitReceive());
    }

    @Override
    public void waitReceive(OrderContext context) {

    }

    @Override
    public void confirmReceived(OrderContext context) {

    }

    @Override
    public void orderEvaluation(OrderContext context) {

    }

    @Override
    public void finish(OrderContext context) {

    }
}

待收貨

package com.lxl.www.designPatterns.statePattern.order2;

/**
 * 待收貨
 */
public class WaitReceive implements IOrderStatus {

    public WaitReceive() {
        System.out.println("當前狀態 :: 待收貨");
    }

    @Override
    public void pending(OrderContext context) {

    }

    @Override
    public void cancelPay(OrderContext context) {

    }

    @Override
    public void waitOut(OrderContext context) {

    }

    /*
     * 退貨退款
     */
    public void refunds(OrderContext context) {
        System.out.println("申請退貨");
        context.setOrderStatus(new Refunds());
    }

    @Override
    public void waitReceive(OrderContext context) {
        System.out.println("進行收貨, 完成收貨動作");
        context.setOrderStatus(new Confirm());

    }

    @Override
    public void confirmReceived(OrderContext context) {

    }

    @Override
    public void orderEvaluation(OrderContext context) {

    }

    @Override
    public void finish(OrderContext context) {

    }
}

確認收貨

package com.lxl.www.designPatterns.statePattern.order2;

public class Confirm implements IOrderStatus{

    public Confirm() {
        System.out.println("當前狀態 :: 確認收貨");
    }
    @Override
    public void pending(OrderContext context) {

    }

    @Override
    public void cancelPay(OrderContext context) {

    }

    @Override
    public void waitOut(OrderContext context) {

    }

    @Override
    public void refunds(OrderContext context) {
        System.out.println("申請退款");
        context.setOrderStatus(new Refunds());
    }

    @Override
    public void waitReceive(OrderContext context) {

    }

    @Override
    public void confirmReceived(OrderContext context) {
        System.out.println("確認收貨了");
        context.setOrderStatus(new OrderEvaluation());
    }

    @Override
    public void orderEvaluation(OrderContext context) {

    }

    @Override
    public void finish(OrderContext context) {

    }
}

訂單評價

package com.lxl.www.designPatterns.statePattern.order2;

import com.sun.tools.corba.se.idl.constExpr.Or;

/**
 * 訂單評價
 */
public class OrderEvaluation implements IOrderStatus {

    public OrderEvaluation() {
        System.out.println("當前狀態 :: 訂單待評價");
    }

    @Override
    public void pending(OrderContext context) {

    }

    @Override
    public void cancelPay(OrderContext context) {

    }

    @Override
    public void waitOut(OrderContext context) {

    }

    @Override
    public void refunds(OrderContext context) {

    }

    @Override
    public void waitReceive(OrderContext context) {

    }

    @Override
    public void confirmReceived(OrderContext context) {

    }

    @Override
    public void orderEvaluation(OrderContext context) {
        System.out.println("訂單評價完了");
        context.setOrderStatus(new Finish());
    }

    @Override
    public void finish(OrderContext context) {

    }
}

訂單完成

package com.lxl.www.designPatterns.statePattern.order2;

/**
 * 訂單完成
 */
public class Finish implements IOrderStatus {

    @Override
    public void pending(OrderContext context) {

    }

    @Override
    public void cancelPay(OrderContext context) {

    }

    @Override
    public void waitOut(OrderContext context) {

    }

    /*
     * 退貨退款
     */
    public void refunds(OrderContext context) {

    }

    @Override
    public void waitReceive(OrderContext context) {

    }

    @Override
    public void confirmReceived(OrderContext context) {

    }

    @Override
    public void orderEvaluation(OrderContext context) {

    }

    @Override
    public void finish(OrderContext context) {
        System.out.println("訂單完成");
    }
}

取消付款

package com.lxl.www.designPatterns.statePattern.order2;

/**
 * 取消付款
 */
public class CancelPay implements IOrderStatus {

    @Override
    public void pending(OrderContext context) {

    }

    /*
     * 取消付款
     */
    public void cancelPay(OrderContext context) {

    }

    @Override
    public void waitOut(OrderContext context) {

    }

    /*
     * 退貨退款
     */
    public void refunds(OrderContext context) {

    }

    @Override
    public void waitReceive(OrderContext context) {

    }

    @Override
    public void confirmReceived(OrderContext context) {

    }

    @Override
    public void orderEvaluation(OrderContext context) {

    }

    @Override
    public void finish(OrderContext context) {

    }
}

退貨退款

package com.lxl.www.designPatterns.statePattern.order2;

public class Refunds implements IOrderStatus{

    public Refunds() {
        System.out.println("當前狀態 :: 退貨退款");
    }

    @Override
    public void pending(OrderContext context) {

    }

    @Override
    public void cancelPay(OrderContext context) {

    }

    @Override
    public void waitOut(OrderContext context) {

    }

    @Override
    public void refunds(OrderContext context) {
        System.out.println("退款完成");
        context.setOrderStatus(new Finish());
    }

    @Override
    public void waitReceive(OrderContext context) {

    }

    @Override
    public void confirmReceived(OrderContext context) {

    }

    @Override
    public void orderEvaluation(OrderContext context) {

    }

    @Override
    public void finish(OrderContext context) {

    }
}

我們看得出來, 這些狀態子類,繼承了父類所有的方法,但是卻沒有實現所有的方法。而是只實現了和自己有關係的一部分方法。這也是我們後面需要優化的地方

第四步: 客戶端調用

package com.lxl.www.designPatterns.statePattern.order2;

public class OrderClient {
    public static void main(String[] args) {
        System.out.println("==========張三   開始下單==========");
        OrderContext o1 = new OrderContext();

        System.out.println("===========取消付款==============");
        o1.cancelPay();
        o1.finish();


        System.out.println();
        System.out.println();
        System.out.println("==========李四    開始下單==========");
        OrderContext o2 = new OrderContext();

        System.out.println("===========付款==============");
        o2.pending();

        System.out.println("===========退貨退款==============");
        o2.refunds();
        o2.refunds();
        o2.finish();


        System.out.println();
        System.out.println();
        System.out.println("==========王五    開始下單==========");
        OrderContext o3 = new OrderContext();

        System.out.println("===========付款==============");
        o3.pending();

        System.out.println("===========出庫==============");
        o3.waitOut();

        System.out.println("===========收貨==============");
        o3.waitReceive();

        System.out.println("===========確認收貨==============");
        o3.confirmReceived();

        System.out.println("===========訂單評價==============");
        o3.orderEvaluation();

        System.out.println("===========訂單完成==============");
        o3.finish();
    }
}

來看看運行效果:

張三 開始下單

開始購物

當前狀態::待付款

=取消付款====

取消付款

訂單完成

李四 開始下單

開始購物

當前狀態::待付款

=付款

付款完成了, 待出庫

當前狀態::待出庫

=退貨退款

申請退貨

當前狀態 :: 退貨退款

退款完成

訂單完成

王五 開始下單

開始購物

當前狀態::待付款

=付款

付款完成了, 待出庫

當前狀態::待出庫

=出庫

出庫完成, 待收貨

當前狀態 :: 待收貨

=收貨

進行收貨, 完成收貨動作

當前狀態 :: 確認收貨

=確認收貨

確認收貨了

當前狀態 :: 訂單待評價

=訂單評價

訂單評價完了

=訂單完成

訂單完成

第三種:完善方案二

方案二違背了接口隔離原則。胖接口,導致很多類其實都不需要重寫方法,但是不得不繼承。實際工作中,我們可以將胖接口拆分,這樣狀態子類只需要實現自己需要的接口就可以了。

今天這裡還有一種特別的解決方案,也可以很巧妙的解決這個問題。

第一步: 定義環境上下文類

觀察一下,這次的環境上下文類和方案二的有什麼區別?

對每一個狀態進行的強轉。這樣做的優點後面就可以看出來了

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 訂單上下文
 */
public class OrderContext {

    /**在上下文類中記錄訂單狀態*/
    private IOrderStatus orderStatus;

    public OrderContext() {
        System.out.println("開始購物");
        // 最開始是待付款狀態
        this.orderStatus = new Pending();
    }

    public void setOrderStatus(IOrderStatus orderStatus) {
        this.orderStatus = orderStatus;
    }


    /**
     * 去付款
     */
    public void toPay() {
        ((Pending)this.orderStatus).toPay(this);
    }

    /**
     * 取消付款
     */
    public void toCancelPay() {
        ((Pending)this.orderStatus).toCancelPay(this);
    }

    /**
     * 取消付款
     */
    public void cancelPay() {
        ((CancelPay)this.orderStatus).cancelPay(this);
    }

    /**
     * 去發貨
     */
    public void toSendProduct() {
        ((WaitOut)this.orderStatus).toSendProduct(this);
    }

    /**
     * 申請退款
     */
    public void toRefunds() {
        ((Confirm)this.orderStatus).toRefunds(this);
    }

    /**
     * 已退款
     */
    public void refunded() {
        ((Refunds)this.orderStatus).refunded(this);
    }

    public void toReceiveProduct() {
        ((WaitReceive)this.orderStatus).toReceiveProduct(this);
    }

    /**
     * 點擊確認收貨按鈕
     */
    public void toConfirmReceived() {
        ((Confirm)this.orderStatus).toConfirmReceived(this);
    }

    /**
     * 去評價訂單
     */
    public void toOrderEvaluation() {
        ((OrderEvaluation)this.orderStatus).toOrderEvaluation(this);
    }

    /**
     * 已完成
     */
    public void finish() {
        ((Finish)this.orderStatus).finish(this);
    }
}

第二步: 定義抽象狀態類

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 訂單狀態
 */
public interface IOrderStatus {

}

抽象狀態類裏面並沒有方法

第三步: 定義具體狀態實現

待付款

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 待付款
 */
public class Pending implements IOrderStatus {
    public Pending() {
        System.out.println("當前狀態::待付款");

    }

    public void toPay(OrderContext context) {
        System.out.println("付款完成了, 待出庫");
        context.setOrderStatus(new WaitOut());
    }

    /*
     * 取消付款
     */
    public void toCancelPay(OrderContext context) {
        System.out.println("不想要了, 要取消付款");
        context.setOrderStatus(new CancelPay());
    }

}

待出庫

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 待出庫
 */
public class WaitOut implements IOrderStatus {

    public WaitOut() {
        System.out.println("當前狀態::待出庫");
    }


    /*
     * 退貨退款
     */
    public void toRefunds(OrderContext context) {
        System.out.println("不想要了, 申請退貨退款");
        context.setOrderStatus(new Refunds());
    }

    /**
     * 去發貨
     * @param context
     */
    public void toSendProduct(OrderContext context) {
        System.out.println("出庫完成, 待收貨");
        context.setOrderStatus(new WaitReceive());
    }
}

待收貨

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 待收貨
 */
public class WaitReceive implements IOrderStatus {

    public WaitReceive() {
        System.out.println("當前狀態 :: 待收貨");
    }

    /*
     * 退貨退款
     */
    public void refunds(OrderContext context) {
        System.out.println("申請退貨");
        context.setOrderStatus(new Refunds());
    }

    /**
     * 去收穫
     * @param context
     */
    public void toReceiveProduct(OrderContext context) {
        System.out.println("執行收貨邏輯, 完成收貨操作");
        context.setOrderStatus(new Confirm());

    }
}

確認收貨

package com.lxl.www.designPatterns.statePattern.order3;

public class Confirm implements IOrderStatus {

    public Confirm() {
        System.out.println("當前狀態 :: 確認收貨");
    }

    public void toRefunds(OrderContext context) {
        System.out.println("不想要了, 想退款");
        context.setOrderStatus(new Refunds());
    }


    public void toConfirmReceived(OrderContext context) {
        System.out.println("執行確認收貨邏輯");
        context.setOrderStatus(new OrderEvaluation());
    }
}

訂單評價

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 訂單評價
 */
public class OrderEvaluation implements IOrderStatus {

    public OrderEvaluation() {
        System.out.println("當前狀態 :: 訂單待評價");
    }

    public void toOrderEvaluation(OrderContext context) {
        System.out.println("訂單評價完了");
        context.setOrderStatus(new Finish());
    }
}

訂單完成

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 訂單完成
 */
public class Finish implements IOrderStatus {

    public void finish(OrderContext context) {
        System.out.println("訂單完成");
    }
}

取消付款

package com.lxl.www.designPatterns.statePattern.order3;

/**
 * 取消付款
 */
public class CancelPay implements IOrderStatus {


    /*
     * 取消付款
     */
    public void cancelPay(OrderContext context) {
        System.out.println("執行取消付款邏輯");
        context.setOrderStatus(new Finish());
    }

}

退貨退款

package com.lxl.www.designPatterns.statePattern.order3;

public class Refunds implements IOrderStatus {

    public Refunds() {
        System.out.println("當前狀態 :: 退貨退款");
    }


    public void refunded(OrderContext context) {
        System.out.println("完成退款");
        context.setOrderStatus(new Finish());
    }
}

第四步:客戶端調用

package com.lxl.www.designPatterns.statePattern.order3;

public class OrderClient {
    public static void main(String[] args) {
        System.out.println("==========張三   開始下單==========");
        OrderContext o1 = new OrderContext();

        System.out.println("===========取消付款==============");
        o1.toCancelPay();
        o1.cancelPay();


        System.out.println("");
        System.out.println("");
        System.out.println("==========李四    開始下單  收貨後退款==========");
        OrderContext o2 = new OrderContext();

        System.out.println("===========付款==============");
        o2.toPay();

        System.out.println("===========發貨==============");
        o2.toSendProduct();
        System.out.println("===========收穫==============");
        o2.toReceiveProduct();
        System.out.println("===========退貨==============");
        o2.toRefunds();
        o2.refunded();
        System.out.println("===========完成==============");
        o2.finish();


        System.out.println();
        System.out.println();
        System.out.println("==========王五    開始下單走完全流程==========");
        OrderContext o3 = new OrderContext();

        System.out.println("===========付款==============");
        o3.toPay();

        System.out.println("===========出庫==============");
        o3.toSendProduct();

        System.out.println("===========收貨==============");
        o3.toReceiveProduct();

        System.out.println("===========確認收貨==============");
        o3.toConfirmReceived();

        System.out.println("===========訂單評價==============");
        o3.toOrderEvaluation();

        System.out.println("===========訂單完成==============");
        o3.finish();

    }
}

運行結果:

張三 開始下單

開始購物

當前狀態::待付款

=取消付款====

不想要了, 要取消付款

執行取消付款邏輯

李四 開始下單 收貨後退款

開始購物

當前狀態::待付款

=付款

付款完成了, 待出庫

當前狀態::待出庫

=發貨

出庫完成, 待收貨

當前狀態 :: 待收貨

=收穫

執行收貨邏輯, 完成收貨操作

當前狀態 :: 確認收貨

=退貨

不想要了, 想退款

當前狀態 :: 退貨退款

完成退款

=完成====

訂單完成

王五 開始下單走完全流程

開始購物

當前狀態::待付款

=付款

付款完成了, 待出庫

當前狀態::待出庫

=出庫

出庫完成, 待收貨

當前狀態 :: 待收貨

=收貨

執行收貨邏輯, 完成收貨操作

當前狀態 :: 確認收貨

=確認收貨

執行確認收貨邏輯

當前狀態 :: 訂單待評價

=訂單評價

訂單評價完了

=訂單完成

訂單完成

運行結果和方案二是一樣的

四、狀態設計模式的優缺點

優點:

  • 結構清晰,避免了過多的switch…case或if…else語句的使用
  • 代碼有很強的可讀性。狀態模式將每個狀態的行為封裝到對應的一個類中
  • 狀態類職責明確,有利於程序的擴展。通過定義新的子類很容易地增加新的狀態和轉換。
  • 封裝性非常好,狀態變化放置到了類的內部來實現,外部調用不需要知道類內部如何實現狀態和行為的變換

缺點:

  • 會產生很多類。每個狀態都要一個對應的類,當狀態過多時會產生很多類,加大維

    護難度

  • 違背開閉原則:增加一個狀態,除了要增加狀態子類,還需要修改原來的環境類。

五、應用場景

當一個事件或者對象有很多種狀態,狀態之間會相互轉換,對不同的狀態要求有不同的行為的時候,可以考慮使用狀態模式

  • 電梯,有運行狀態、開門狀態、閉門狀態、停止狀態等
  • 一日從早到晚自身的狀態,比如工作狀態、學習狀態、睡覺狀態等等
  • 運動員可以有正常狀態、非正常狀態和超長狀態

六、注意事項

  • 在行為受狀態約束的情況下可以使用狀態模式,使用時對象的狀態最好不要超過5個。因為狀態越多,邏輯越負責,後期維護成本越高。

七、狀態模式對六大原則的使用分析

  • 單一職責原則: 符合單一職責原則,一個狀態類只做一件事
  • 里式替換原則:父類出現的地方都可以使用子類替換。— 狀態模式父類是抽象類或者接口。方案一重寫了父類的構造方法
  • 依賴倒置原則:依賴於抽象,而不是依賴於具體。—符合
  • 接口隔離原則:依賴於最小接口—方案二不符合
  • 迪米特法則:最小知識原則-之和朋友交流,減少和朋友的溝通 — 符合
  • 開閉原則:對擴展開放,對修改關閉—-增加狀態需要修改原來的環境類—不符合