這是我參與8月更文挑戰的第4天,活動詳情查看: 8月更文挑戰html
😀 週一搬磚,元氣滿滿,繼續啃設計模式之美,本文對應設計模式與範式:行爲型(64),狀態模式 (State Pattern),描述了對象 狀態變化 及如何在每種狀態下表現出不一樣的 行爲~java
Tips:二手知識加工不免有所紕漏,感興趣有時間的可自行查閱原文,謝謝。算法
原始定義設計模式
容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了本身的類同樣。數組
簡單點說markdown
讓一個對象經過一系列狀態的變化來控制行爲的變化。app
狀態模式
和 策略模式
極其類似,可經過內在差異進行區分:ide
有home鍵的Android機爲例,按下home鍵,處於不一樣狀態有不一樣的行爲:函數
不使用狀態模式實現一波:oop
public class StateTest {
private static int state = 0;
private final static int CLOSE = 0; // 關機狀態
private final static int FIRST_BOOT = 1; // 首次啓動
private final static int NOT_FIRST_BOOT = 2; // 非首次啓動
private final static int AFTER_BOOT = 3; // 啓動後
private static void clickHome() {
if(state == CLOSE) {
System.out.println("處於關機狀態,沒有反應");
} else if(state == FIRST_BOOT) {
System.out.println("首次啓動。能夠進行密碼解鎖");
} else if(state == NOT_FIRST_BOOT) {
System.out.println("非首次啓動,能夠進行密碼或指紋解鎖");
} else if(state == AFTER_BOOT) {
System.out.println("啓動狀態,返回主界面");
}
}
public static void main(String[] args) {
state = CLOSE;
clickHome();
state = FIRST_BOOT;
clickHome();
state = NOT_FIRST_BOOT;
clickHome();
state = AFTER_BOOT;
clickHome();
}
}
複製代碼
代碼運行結果以下:
若是須要增長一種狀態,如處於fastboot模式,狀態定義要寫一個,而後if-else加一個判斷;還有,不止處理Home鍵,還有音量鍵、電源鍵,又得定義幾個函數,而後複製一波這個if-else,試試用狀態模式實現一波。
// 抽象狀態
public abstract class State {
protected StateContext context;
public void setContext(StateContext context) { this.context = context; }
abstract void onHomeClick();
abstract void onPowerClick();
abstract void onVolumeAscClick();
abstract void onVolumeDescClick();
}
// 具體狀態 → 關機狀態
public class CloseState extends State {
@Override public void onHomeClick() { System.out.println("處於關機狀態,按Home鍵沒有反應"); }
@Override void onPowerClick() {
System.out.println("手機開機");
context.setState(FirstBootState.class);
context.setScreenOn(true);
context.getState().onHomeClick();
}
@Override void onVolumeAscClick() { System.out.println("處於關機狀態,按音量+沒反應"); }
@Override void onVolumeDescClick() { System.out.println("處於關機狀態,按音量-沒反應"); }
}
// 具體狀態 → 第一次啓動狀態
public class FirstBootState extends State {
@Override public void onHomeClick() {
System.out.println("首次啓動,能夠進行密碼解鎖");
System.out.println("解鎖完畢,進入主界面");
context.setState(AfterBootState.class);
context.setScreenOn(true);
}
@Override void onPowerClick() {
if(context.isScreenOn()) {
System.out.println("熄屏");
} else {
System.out.println("亮屏,等待密碼解鎖");
}
context.setScreenOn(!context.isScreenOn());
}
@Override void onVolumeAscClick() { System.out.println("音量+"); }
@Override void onVolumeDescClick() { System.out.println("音量-"); }
}
// 具體狀態 → 非第一次啓動狀態
public class NotFirstBootState extends State {
@Override public void onHomeClick() {
System.out.println("非首次啓動,能夠經過密碼或指紋解鎖");
System.out.println("解鎖完畢,進入主界面");
context.setScreenOn(true);
context.setState(AfterBootState.class);
}
@Override void onPowerClick() {
if(context.isScreenOn()) {
System.out.println("熄屏");
} else {
System.out.println("亮屏,等待密碼或指紋解鎖");
context.setState(NotFirstBootState.class);
}
context.setScreenOn(!context.isScreenOn());
}
@Override void onVolumeAscClick() { System.out.println("音量+"); }
@Override void onVolumeDescClick() { System.out.println("音量-"); }
}
// 具體狀態 → 啓動後
public class AfterBootState extends State {
@Override void onHomeClick() { System.out.println("返回主界面"); }
@Override void onPowerClick() {
if(context.isScreenOn()) {
System.out.println("熄屏");
context.setState(NotFirstBootState.class);
} else {
System.out.println("亮屏,等待密碼或指紋解鎖");
context.getState().onHomeClick();
}
context.setScreenOn(!context.isScreenOn());
}
@Override void onVolumeAscClick() { System.out.println("音量+"); }
@Override void onVolumeDescClick() { System.out.println("音量-"); }
}
// 上下文信息類
public class StateContext {
private boolean isScreenOn = false; // 屏幕是否亮着
public final static Map<Class, State> stateMap = new HashMap<>();
private State state; // 手機當前狀態
static {
stateMap.put(CloseState.class, new CloseState());
stateMap.put(FirstBootState.class, new FirstBootState());
stateMap.put(NotFirstBootState.class, new NotFirstBootState());
stateMap.put(AfterBootState.class, new AfterBootState());
}
public void setState(Class stateClass) {
this.state = stateMap.get(stateClass);
this.state.setContext(this);
}
public State getState() { return state; }
public boolean isScreenOn() { return isScreenOn; }
public void setScreenOn(boolean screenOn) {
isScreenOn = screenOn;
System.out.println("===> 屏幕處於:" + (isScreenOn ? "亮屏狀態": "熄屏狀態"));
}
}
// 測試用例
public class StateTest {
public static void main(String[] args) {
StateContext context = new StateContext();
context.setState(CloseState.class);
// 處於關機狀態點擊音量- 和 home鍵
context.getState().onVolumeDescClick();
context.getState().onHomeClick();
// 處於關機狀態點擊電源鍵
context.getState().onPowerClick();
context.getState().onPowerClick();
context.getState().onHomeClick();
context.getState().onVolumeAscClick();
}
}
複製代碼
代碼運行結果以下:
經過狀態模式,咱們把事件觸發的 狀態轉移和動做執行,拆分到不一樣的狀態類中,避免了分支判斷結構。
順帶帶出UML類圖、組成角色、使用場景及優缺點~
使用場景:
優勢:
缺點:
英文翻譯 Finite State Machine,縮寫FSM,簡稱狀態機,它有三個組成部分:狀態(State)
、事件(Event)
、動做(Action)
。其中的事件又稱爲 轉移條件
,事件觸發狀態的轉移和動做的執行(非必須)。
也能夠理解爲一種數學模型,該模型中有幾個狀態(有限的),在不一樣場景下,不一樣的狀態間發生轉移,在狀態轉移過程當中可能伴隨着不一樣的事件發生。
狀態機有三種常見的實現方式:
在Android系統源碼中涉及到 多狀態 管理老是經過十六進制數字來表示,如ViewGroup中:
static final int FLAG_CLIP_CHILDREN = 0x1;
private static final int FLAG_CLIP_TO_PADDING = 0x2;
static final int FLAG_INVALIDATE_REQUIRED = 0x4;
private static final int FLAG_RUN_ANIMATION = 0x8;
static final int FLAG_ANIMATION_DONE = 0x10;
private static final int FLAG_PADDING_NOT_NULL = 0x20;
private static final int FLAG_ANIMATION_CACHE = 0x40;
static final int FLAG_OPTIMIZE_INVALIDATE = 0x80;
static final int FLAG_CLEAR_TRANSFORMATION = 0x100;
private static final int FLAG_NOTIFY_ANIMATION_LISTENER = 0x200;
複製代碼
這是爲何呢?先複習下幾種二進制運算:
按位與(&)
→ 對應位都爲1才爲1,不然爲0,如0x1 & 0x2 → 0001 & 0010 → 0000;按位或(|)
→ 對應位有一個爲1即爲1,如0x1 | 0x2 → 0001 | 0010 → 0011取反(~)
→ 按位取反,如~0x1 → 0001 → 1110接着以上面手機狀態爲例,寫個狀態管理的例子:
private static int state = 0;
private final static int CLOSE = 0x1; // 關機狀態
private final static int FIRST_BOOT = 0x2; // 首次啓動
private final static int NOT_FIRST_BOOT = 0x4; // 非首次啓動
private final static int AFTER_BOOT = 0x8; // 啓動後
複製代碼
狀態增長 → 或運算
state | CLOSE → (0000 | 0001) → 0001 → 此時狀態:CLOSE
state | FIRST_BOOT → (0001 | 0010) → 0011 → 此時狀態:CLOSE + FIRST_BOOT
state | NOT_FIRST_BOOT → (0011 | 0100) → 0111 → 此時狀態:CLOSE + FIRST_BOOT + NOT_FIRST_BOOT
複製代碼
狀態移除 → 對應的位數從1改成0,先取反,再與運算
state &= ~NOT_FIRST_BOOT → (0111 & 1011) → 0011 → 此時狀態:CLOSE + FIRST_BOOT
state &= ~CLOSE → (0011 & 1110) → 0010 → 此時狀態:FIRST_BOOT
複製代碼
狀態判斷 → 與運算判斷結果是否爲0
// 假設此時狀態爲:CLOSE + FIRST_BOOT + NOT_FIRST_BOOT
state & FIRST_BOOT → 0111 & 0010 = 0010 → 0010 → 結果不爲0,包含此狀態;
// 假設此時狀態爲:CLOSE + FIRST_BOOT
state & NOT_FIRST_BOOT → 0011 & 0100 → 結果爲0,不包含此狀態;
複製代碼
疑惑:用來標識狀態的十六進制並非連續的,如跳過了0x3:
若是把上面的NOT_FIRST_BOOT從0x4改成0x3,而CLOSE + FIRST_BOOT 結果爲0011,同爲0x3,此時進行狀態判斷結果不爲0,難道說增長了NOT_FIRST_BOOT狀態嗎?因此這裏的取值是有固定規則的,即 左移一位。
private final static int CLOSE = 1 << 0;
private final static int FIRST_BOOT = 1 << 1;
private final static int NOT_FIRST_BOOT = 1 << 2;
private final static int AFTER_BOOT = 1 << 3;
複製代碼
選擇十六進制的緣由而不用其餘進制的緣由(如十進制):
計算機中,一個字節有八位,最大值爲1111111,對應十進制255,十六進制FF,半個字節用十六進制 經過一個字母 就能表示,而轉換成十進制則是一個無規律的數字,相比起十進制,十六進制轉二進制 更直觀一些。
參考文獻: