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的缺點很明顯
- 違背開閉原則: 當增加一種狀態的時候, 需要修改原來的邏輯
- 當狀態很多的時候, 代碼段很長, 臃腫, 不容易維護, 可擴展性差.
狀態模式可以很好地解決這個問題。
狀態模式的思想:當是條件語句表示一個對象狀態轉換過於複雜時,可以把條件判斷中的「判斷邏輯」提取出來,放在單獨的類中,當前上下文處於那種狀態,直接用相應的狀態類對象進行處理,這樣的好處是:能把原來複雜的判斷邏輯簡單化,消除了 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();
}
}
客戶端直接結果分析:
- Context實例花的時候, 狀態是 ConcreteStateA
- 第一次執行context.handler1();將狀態切換到了ConcreteStateB
- 第二次執行context.handler1();將狀態切換到了ConcreteStateC
- 第三次執行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個。因為狀態越多,邏輯越負責,後期維護成本越高。
七、狀態模式對六大原則的使用分析
- 單一職責原則: 符合單一職責原則,一個狀態類只做一件事
- 里式替換原則:父類出現的地方都可以使用子類替換。— 狀態模式父類是抽象類或者接口。方案一重寫了父類的構造方法
- 依賴倒置原則:依賴於抽象,而不是依賴於具體。—符合
- 接口隔離原則:依賴於最小接口—方案二不符合
- 迪米特法則:最小知識原則-之和朋友交流,減少和朋友的溝通 — 符合
- 開閉原則:對擴展開放,對修改關閉—-增加狀態需要修改原來的環境類—不符合