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語句的使用,避免了程序的復雜性,提高系統的可維護性; - 遵循設計原則
很好地體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就要增加子類,你要修改狀態,你只修改一個子類就可以了。 - 封裝性非常好
這也是狀態模式的基本要求,狀態變換放置到類的內部來實現,外部的調用不用知道類內部如何實現狀態和行為的變換。
缺點
子類會太多,也就是類膨脹。如果一個事物有很多個狀態也不稀奇,如果完全使用狀態模式就會有太多的子類,不好管理,這個需要大家在項目中自己衡量。其實有很多方式可以解決這個狀態問題,如在數據庫中建立一個狀態表,然后根據狀態執行相應的操作,這個也不復雜,看大家的習慣和嗜好了。