1. 程式人生 > >狀態模式(State Pattern)。

狀態模式(State Pattern)。

定義

當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。

狀態模式的核心是封裝,狀態的變更引起了行為的變更,從外部看起來就好像這個物件對應的類發生了改變一樣。

我們先來看看狀態模式中的3個角色。

  • State——抽象狀態角色

介面或抽象類,負責物件狀態定義,並且封裝環境以實現狀態切換。

  • ConcreteState——具體狀態角色

每一個具體狀態必須完成兩個職責:本狀態的行為管理以及趨向狀態處理,通俗的說,就是本狀態下要做的事情,以及本狀態如何過渡到其他狀態。

  • Context——環境角色

定義客戶端需要的介面,並且負責具體狀態的切換。

狀態模式相對來說比較複雜,他提供了一種對物質運動的另一個觀察視角,通過狀態變更促使行為的變化,就類似水的狀態變更一樣,一碗水的初始狀態是液態,通過加熱轉變為氣態,狀態的改變同時也引起體積的擴大,然後就產生了一個新的行為:鳴笛或頂起壺蓋,瓦特就是這麼發明蒸汽機的。

通用原始碼

我們再來看看狀態模式的通用原始碼,首先來看抽象環境角色,如下所示。

public abstract class State {
	// 定義一個環境角色,提供子類訪問
	protected Context context;

	/**
	 * 設定環境角色
	 * 
	 * @param context
	 */
	public void setContext(Context context) {
		this.context = context;
	}

	/**
	 * 行為1
	 */
	public abstract void handle1();

	/**
	 * 行為2
	 */
	public abstract void handle2();
}

抽象環境中宣告一個環境角色,提供各個狀態類自行訪問,並且提供所有狀態的抽象行為,由各個實現類實現。具體環境角色如下所示。

public class ConcreteState1 extends State {

	@Override
	public void handle1() {
		// 本狀態下必須處理的邏輯
	}

	@Override
	public void handle2() {
		// 設定當前狀態為state2
		super.context.setCurrentState(Context.STATE2);
		// 過濾到state2狀態,由Context實現
		super.context.handle2();
	}

}
public class ConcreteState2 extends State {

	@Override
	public void handle1() {
		// 設定當前狀態為state1
		super.context.setCurrentState(Context.STATE1);
		// 過濾到state1狀態,由Context實現
		super.context.handle1();
	}

	@Override
	public void handle2() {
		// 本狀態下必須處理的邏輯
	}

}

具體環境角色有兩個職責:處理本狀態必須完成的任務,決定是否可以過渡到其他狀態。我們再來看環境角色,如下所示。

public class Context {
	// 定義狀態
	public final static State STATE1 = new ConcreteState1();
	public final static State STATE2 = new ConcreteState2();
	// 當前狀態
	private State CurrentState;

	public State getCurrentState() {
		return CurrentState;
	}

	public void setCurrentState(State currentState) {
		CurrentState = currentState;
		// 切換狀態
		this.CurrentState.setContext(this);
	}

	/**
	 * 行為委託
	 */
	public void handle1() {
		this.CurrentState.handle1();
	}

	/**
	 * 行為委託
	 */
	public void handle2() {
		this.CurrentState.handle2();
	}
}

環境角色有兩個不成文的約束:

  • 把狀態物件宣告為靜態常量,有幾個狀態物件就宣告幾個靜態常量。
  • 環境角色具有狀態抽象角色定義的所有行為,具體執行使用委託方式。

我們再來看場景類如何執行,如下所示。

public class Client {
	public static void main(String[] args) {
		// 定義環境角色
		Context context = new Context();
		// 初始化狀態
		context.setCurrentState(new ConcreteState1());
		// 行為執行
		context.handle1();
		context.handle2();
	}
}

我們已經隱藏了狀態的變化過程,他的切換引起了行為的變化。對外來說,我們只看到行為的發生改變,而不用知道是狀態變化引起的。

優點

  • 結構清晰

避免了過多的switch...case或者if...else語句的使用,避免了程式的複雜性,提高系統的可維護性。

  • 遵循設計原則

很好的體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就要增加子類,你要修改狀態,你只修改一個子類就可以了。

  • 封裝性非常好

這也是狀態模式的基本要求,狀態變換放置到類的內部來實現,外部的呼叫不用知道類內部如何實現狀態和行為的變換。

缺點

只有一個缺點,子類會太多,也就是類膨脹。如果一個事物有很多個狀態也不稀奇,如果完全使用狀態模式就會有太多的子類,不好管理,這個需要大家在專案中自己衡量。其實有很多方式可以解決這個狀態問題,如在資料庫中建立一個狀態表,然後根據狀態執行相應的操作,這個也不復雜,看大家的習慣和嗜好了。

使用場景

  • 行為隨狀態改變而改變的場景

這也是狀態模式的根本出發點,例如許可權設計,人員的狀態不同即使執行相同的行為結果也會不同,在這種情況下需要考慮使用狀態模式。

  • 條件、分支判斷語句的替代者

在程式中大量使用switch語句或者if判斷語句會導致程式結構不清晰,邏輯混亂,使用狀態模式可以很好的避免這一問題,他通過擴充套件子類實現了條件的判斷處理。

注意事項

狀態模式適用於當某個物件在他的狀態發生改變時,他的行為也隨著發生比較大的變化,也就是說在行為受狀態約束的情況下可以使用狀態模式,而且使用時物件的狀態最好不要超過5個。

最佳實踐

狀態間的自由切換,那會有很多種,你要挨個牢記一遍嗎?比如電梯的例子,我要一個正常的電梯執行邏輯,規則是開門→關門→執行→停止;還要一個緊急狀態(如火災)下的執行邏輯,關門→停止,緊急狀態時,電梯當然不能用了;再要一個維修狀態下的執行邏輯,這個狀態任何情況都可以,開著門電梯執行?可以!門來回關?可以!永久停止不動?可以!那這怎麼實現呢?需要我們把已經有的幾種狀態按照一定的順序再重新組裝一下,那這個是什麼模式?建造者模式,對建造模式+狀態模式會起打非常好的封裝作用。

更進一步,應該有做過工作流開發,如果不是土製框架,那麼就應該有個狀態機管理(即使是土製框架也應該有),如一個Activity(節點)有初始化狀態(Initialized State)、掛起狀態(Suspended State)、完成狀態(Completed State)等,流程例項也有這麼多狀態,那這些狀態怎麼管理呢?通過狀態機(State Machine)來管理,那狀態機是什麼東西呢?就是我們上面提到的Context類的升級變態BOSS!