【趣味設計模式系列】之【狀態模式】
阿新 • • 發佈:2020-01-05
1. 簡介
狀態模式(State Pattern),當一個物件內在狀態改變
時允許其改變行為
,這個物件看起來像改變了其類。簡而言之,就是狀態的變更引起了行為的變更
。
2. 圖解
下圖四輛汽車,分別代表汽車日常的四種狀態。
開門狀態:
關門狀態:
飛奔狀態:
停止狀態:
其中,某種特定狀態下,都有四個可執行操作,分別是open,close,run,stop,然後做對應的處理得下圖所示。
3. 案例實現
類圖如下:
- 定義汽車抽象狀態類
CarState
,持有型別為Context
的屬性,同時持有四個可執行操作,open
,close
,run
,stop
方法; - 定義汽車抽象狀態類的子類
OpenningState
ClosingState
,RunningState
,StoppingState
,分別代表開門狀態,關門狀態,飛奔狀態,停止狀態; - 定義環境角色類
Context
,把狀態物件宣告為靜態常量,有幾個狀態物件就宣告幾個靜態常量,環境角色具有狀態抽象角色定義的所有行為,具體執行使用委託方式。具體環境角色有兩個職責:處理本狀態必須完成的任務,決定是否可以過渡到其他狀態。
程式碼實現如下:
package com.wzj.state.example1; /** * @Author: wzj * @Date: 2019/11/3 20:10 * @Desc: 汽車狀態抽象類 */ public abstract class CarState { //環境角色,封裝狀態變化引起的行為變化 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(); }
package com.wzj.state.example1; /** * @Author: wzj * @Date: 2019/11/3 20:23 * @Desc: 汽車開門狀態類 */ public class OpenningState extends CarState { //開啟汽車門 public void open() { System.out.println("汽車門已開"); } //關閉汽車門 public void close() { //狀態修改 super.context.setCarState(Context.closingState); //動作委託為ClosingState來執行 super.context.getCarState().close(); } //門開著時汽車一般不奔跑 public void run() { System.out.println("汽車開門狀態,不能奔跑"); } //車門開著時,切換不到停止狀態,因為沒有四種狀態中,沒有開門且停止這個狀態 public void stop() { System.out.println("汽車開門狀態,不能長時間開著門且處於停止狀態"); } }
package com.wzj.state.example1;
/**
* @Author: wzj
* @Date: 2019/11/3 20:23
* @Desc: 汽車飛奔狀態類
*/
public class RunningState extends CarState {
//開啟奔跑時不開門
public void open() {
System.out.println("車在飛奔,不能開啟");
}
//奔跑時肯定是關門的
public void close() {
System.out.println("車在飛奔,已經關閉,不能再次關閉");
}
//汽車在飛奔
public void run() {
System.out.println("汽車在飛奔");
}
//汽車可以停下來
public void stop() {
//修改汽車為停止狀態
super.context.setCarState(Context.stoppingState);
//停止動作委託為StoppingState類來執行
super.context.getCarState().stop();
}
}
package com.wzj.state.example1;
/**
* @Author: wzj
* @Date: 2019/11/3 20:23
* @Desc: 汽車關門狀態類
*/
public class ClosingState extends CarState {
//開啟汽車門
public void open() {
//修改汽車為開門狀態
super.context.setCarState(Context.openningState);
//動作委託為OpenningState類來執行
super.context.getCarState().open();
}
//關閉汽車門
public void close() {
System.out.println("汽車門已關");
}
//汽車在飛奔
public void run() {
//修改汽車為飛奔狀態
super.context.setCarState(Context.runningState);
//動作委託為RunningState類來執行
super.context.getCarState().run();
}
//汽車在停止
public void stop() {
//設定汽車狀態為停止狀態
super.context.setCarState(Context.stoppingState);
//動作委託為StoppingState類來執行
super.context.getCarState().stop();
}
}
package com.wzj.state.example1;
/**
* @Author: wzj
* @Date: 2019/11/3 20:19
* @Desc: 上下文環境類
*/
public class Context {
/**列出汽車所有狀態
* openningState-開門狀態 closingState-關門狀態
* runningState-賓士狀態 stoppingState-停止狀態
*/
public static final OpenningState openningState = new OpenningState();
public static final ClosingState closingState = new ClosingState();
public static final RunningState runningState = new RunningState();
public static final StoppingState stoppingState = new StoppingState();
//定義汽車當前狀態
private CarState carState;
public CarState getCarState() {
return carState;
}
public void setCarState(CarState carState) {
this.carState = carState;
//切換狀態
this.carState.setContext(this);
}
//汽車開門
public void open() {
this.carState.open();
}
//汽車關門
public void close(){
this.carState.close();
}
//汽車飛奔
public void run(){
this.carState.run();
}
//汽車停止
public void stop(){
this.carState.stop();
}
}
客戶端類如下:
package com.wzj.state.example1;
/**
* @Author: wzj
* @Date: 2019/11/3 21:06
* @Desc:
*/
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setCarState(new OpenningState());
// context.setCarState(new ClosingState());
// context.setCarState(new RunningState());
// context.setCarState(new StoppingState());
context.open();
// context.close();
// context.run();
// context.stop();
}
}
執行結果如下:
當只打開Client
15行的時候,分別開啟11,12,13,14行的程式碼,會得到如下結果:
汽車為開門狀態時,執行open
汽車為關門狀態時,執行open
汽車為飛奔狀態時,執行open
汽車為停止狀態時,執行open
上述結果可以看出,同樣執行一個open
方法,當狀態的變化時導致行為的變化。
4. 狀態模式總結
優點
- 結構清晰
避免了過多的switch...case或者if...else語句的使用,避免了程式的複雜性,提高系統的可維護性; - 遵循設計原則
很好地體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就要增加子類,你要修改狀態,你只修改一個子類就可以了。 - 封裝性非常好
這也是狀態模式的基本要求,狀態變換放置到類的內部來實現,外部的呼叫不用知道類內部如何實現狀態和行為的變換。
缺點
狀態模式既然有優點,那當然有缺點了。但只有一個缺點,子類會太多,也就是類膨脹。如果一個事物有很多個狀態也不稀奇,如果完全使用狀態模式就會有太多的子類,不好管理,這個需要大家在專案中自己衡量。其實有很多方式可以解決這個狀態問題,如在資料庫中建立一個狀態表,然後根據狀態執行相應的操作,這個也不復雜,看大家的習慣和嗜好了。