概念:狀態模式 允許對象在內部狀態改變時改變它的行動,對象看起來好像修改了它的類
在軟件設計中,我們常常會遇需要編寫有很多狀態的程序。最簡單的如乘坐電梯程序,當我們要坐電梯時需要判斷電梯的狀態,只有當電梯處于當前樓時我們才能乘坐,當電梯不在當前樓層時我們要按下按鈕等待電梯到來。在平時1般都通過 if…else 或 switch 判斷狀態后處理,這類固定的寫法只有在軟件后期不會更新時才可以(不過是不可能的),狀態模式實際上是使用組合通過簡單援用不同的狀態 對象 來造成狀態改變的假象。
Context(上下文):1般是1個具有多個狀態的類,當調用 request() 函數時會被拜托到狀態對象。
State(狀態接口):定義了所有具體狀態的共同接口,任何狀態都實現這個接口,這樣就可以在狀態之間相互轉換。
ConcreteStateA(具體狀態):實現狀態接口,處理來自 Context 的要求。每一個 ConcreteState 都提供自己要求的實現。
我們實現電梯的例子,電梯的狀態包括運行、停止、開門、關門。當電梯運行時我們等待電梯停止,停止時我們等待開門或運行,開門時進入后關門,進入后電梯變成關門狀態。
假定目前就這么多狀態,用傳統的 if…else 和 switch 實現。
抽象狀態類
public abstract class LiftState {
//4種狀態
final int OPEN_STATE = 1;
final int STOP_STATE = 2;
final int CLOSE_STATE = 3;
final int RUN_STATE = 4;
public abstract void setState(int state);
//開門動作
public abstract void open();
//關門動作
public abstract void close();
//運行動作
public abstract void run();
//停止動作
public abstract void stop();
}
public class Lift extends LiftState{
private int state;
public Lift(int state) {
this.state = state;
}
@Override
public void setState(int state) {
this.state = state;
}
//開門動作
@Override
public void open() {
switch (state) {
case OPEN_STATE:
System.out.println("處于開門狀態:甚么也不做");
break;
case CLOSE_STATE:
System.out.println("處于關門狀態:甚么也不做");
break;
case RUN_STATE:
System.out.println("處于運行狀態:甚么也不做");
break;
case STOP_STATE:
System.out.println("處于關門狀態,開門...");
setState(OPEN_STATE);
break;
}
}
//關門動作
@Override
public void close() {
switch (state) {
case OPEN_STATE:
System.out.println("處于開門狀態:關門...");
setState(CLOSE_STATE);
break;
case CLOSE_STATE:
System.out.println("處于關門狀態:甚么也不做");
break;
case RUN_STATE:
System.out.println("處于運行狀態:甚么也不做");
break;
case STOP_STATE:
System.out.println("處于停止狀態:甚么也不做");
break;
}
}
//運行動作
@Override
public void run() {
switch (state) {
case OPEN_STATE:
System.out.println("處于開門狀態:甚么也不做");
break;
case CLOSE_STATE:
System.out.println("處于關門狀態:運行...");
setState(RUN_STATE);
break;
case RUN_STATE:
System.out.println("處于運行狀態:甚么也不做");
break;
case STOP_STATE:
System.out.println("處于停止狀態:運行...");
setState(RUN_STATE);
}
}
//停止動作
@Override
public void stop() {
switch (state) {
case OPEN_STATE:
System.out.println("處于開門狀態:甚么也不做");
break;
case CLOSE_STATE:
System.out.println("處于關門狀態:甚么也不做");
setState(CLOSE_STATE);
break;
case RUN_STATE:
System.out.println("處于運行狀態:停止...");
break;
case STOP_STATE:
System.out.println("處于停止狀態:甚么也不做");
}
}
}
public class LiftRun {
public static void main(String[] args) {
//初始設置為停止狀態
Lift lift = new Lift(2);
//停止變成運行
lift.run();
//運行時不能開門
lift.open();
//運行變成停止
lift.stop();
//停止時不能開門
lift.close();
}
}
上面的實現沒甚么問題,但大量的用了 switch…case,當我們想要為電梯增加1種狀態時,會發現需要大量的改動代碼,每個 switch 都要改,這也是《重構》中稱 switch 為代碼的壞味道。我們來通過 狀態模式 改變它。
LiftState:
public abstract class LiftState {
Lift lift;
public LiftState(Lift lift) {
this.lift = lift;
}
//開門動作
public abstract void open();
//關門動作
public abstract void close();
//運行動作
public abstract void run();
//停止動作
public abstract void stop();
}
CloseState:
public class CloseState extends LiftState {
public CloseState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("處于關閉狀態...甚么也不做");
}
@Override
public void close() {
System.out.println("處于關閉狀態...甚么也不做");
}
@Override
public void run() {
System.out.println("處于關閉狀態:運行");
lift.setState(lift.getRunState());
}
@Override
public void stop() {
System.out.println("處于關閉狀態...甚么也不做");
}
}
OpenState:
public class OpenState extends LiftState {
public OpenState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("處于開門狀態...甚么也不錯");
}
@Override
public void close() {
System.out.println("處于開門狀態:關門");
lift.setState(lift.getCloseState());
}
@Override
public void run() {
System.out.println("處于開門狀態...甚么也不錯");
}
@Override
public void stop() {
System.out.println("處于開門狀態...甚么也不錯");
}
}
RunState:
public class RunState extends LiftState {
public RunState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("處于運行狀態...甚么也不做");
}
@Override
public void close() {
System.out.println("處于運行狀態...甚么也不做");
}
@Override
public void run() {
System.out.println("處于運行狀態...甚么也不做");
}
@Override
public void stop() {
System.out.println("處于運行狀態:停止");
lift.setState(lift.getStopState());
}
}
StopState:
public class StopState extends LiftState {
public StopState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("處于停止狀態:開門");
lift.setState(lift.getOpenState());
}
@Override
public void close() {
System.out.println("處于停止狀態...甚么也不做");
}
@Override
public void run() {
System.out.println("處于停止狀態:運行");
lift.setState(lift.getRunState());
}
@Override
public void stop() {
System.out.println("處于停止狀態...甚么也不做");
}
}
Lift:
public class Lift {
LiftState openState;
LiftState closeState;
LiftState runState;
LiftState stopState;
LiftState state;
public Lift() {
openState = new OpenState(this);
closeState = new CloseState(this);
runState = new RunState(this);
stopState = new StopState(this);
//起始設置為停止狀態
state = stopState;
}
//拜托給狀態對象履行
public void stop() {
state.stop();
}
public void run() {
state.run();
}
public void close() {
state.close();
}
public void open() {
state.open();
}
//僅僅更換當前對象的援用
public void setState(LiftState state) {
this.state = state;
}
public LiftState getOpenState() {
return openState;
}
public LiftState getCloseState() {
return closeState;
}
public LiftState getRunState() {
return runState;
}
public LiftState getStopState() {
return stopState;
}
}
LiftRun:
public class LiftRun {
public static void main(String[] args) {
//其實設置為停止狀態
Lift lift = new Lift();
lift.run();
lift.open();
lift.stop();
lift.close();
}
}
運行結果:
通過狀態模式改進后,我們將每一個狀態和狀態行動封裝成了對象和方法,雖然增加了類的數目,但是當我們增加狀態時不用改動本來的代碼,僅需創建新的狀態實現接口的方法便可。
如增加故障狀態:
public class BugState extends LiftState {
public BugState(Lift lift) {
super(lift);
}
@Override
public void open() {
System.out.println("故障狀態,不能打開");
}
@Override
public void close() {
System.out.println("故障狀態,不能關閉");
}
@Override
public void run() {
System.out.println("故障狀態,不能運行");
}
@Override
public void stop() {
System.out.println("故障狀態,停止..維修");
lift.setState(lift.getStopState());
}
@Override
public void bug() {
System.out.println("故障狀態");
}
}
在每一個狀態對象中加入相應的 bug 狀態函數,如:
@Override
public void bug() {
System.out.println("處于停止狀態:維修電梯");
lift.setState(lift.getBugState());
}
接著在 Lift 類中加入 bug 狀態:
LiftState bugState;
...
//構造函數中
bugState = new BugState(this);
...
public LiftState getBugState() {
return bugState;
}
測試函數:
public class LiftRun {
public static void main(String[] args) {
//其實設置為停止狀態
Lift lift = new Lift();
//運行
lift.run();
lift.bug();
lift.stop();
lift.run();
}
}
處于狀態模式下僅僅需要新增1個類外加做1些小改動便可完成狀態的增加,大大方便了后期的保護和修改。