狀態模式-將狀態和行為封裝成物件
阿新 • • 發佈:2021-01-08
> **公號:碼農充電站pro**
> **主頁:**
本篇文章來介紹**狀態模式**(`State Design Pattern`),狀態模式常用來實現**狀態機**,狀態機常用在遊戲開發等領域。
### 1,狀態模式
**狀態模式**的定義為:允許物件在內部狀態改變時,改變它的行為,物件看起來好像改變了它的類。
狀態模式將狀態和行為封裝成物件,不同的物件有著不同的行為。物件的狀態會因某個行為的發生而改變,物件的狀態一旦改變,那麼物件的行為也會發生改變。
物件的狀態和行為,可以用下面這個圖來解釋。假如一個事物有三種狀態 **1,2,3**,狀態之間的轉換關係如下:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104120235116.png?)
在上面的狀態轉換圖中,每種狀態對應著不同的行為:
- 狀態 **1**:有兩種行為 `a` 和 `b`
- 狀態 **1** 經過 `a` 行為可轉換到狀態 **2**
- 狀態 **1** 經過 `b` 行為可轉換到狀態 **3**
- 狀態 **2**:有兩種行為 `c` 和 `d`
- 狀態 **2** 經過 `c` 行為可轉換到狀態 **1**
- 狀態 **2** 經過 `d` 行為可轉換到狀態 **3**
- 狀態 **3**:有一種行為 `e`
- 狀態 **3** 經過 `e` 行為可轉換到狀態 **1**
狀態模式的類圖如下:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104122456541.png?)
**State** 介面定義了**狀態**可能擁有的所有行為,每個具體的狀態都實現了這個介面,這樣就使得狀態之間可以互相替換。
每個具體狀態對 **State** 介面中的每個行為的實現是不一樣的,這就相當於每個具體狀態的行為是不一樣的。
**StateMachine** 是一個狀態機,它擁有著一個狀態物件,這個狀態物件會不斷的改變。
### 2,遊戲需求
假設我們要為一款遊戲中的角色編寫狀態轉換的程式,並且遊戲角色有積分:
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2021010417351168.png?)
該遊戲中的角色共有 **4** 種狀態 **A,B,C,D**,共有 **3** 種操作 **x,y,z**:
- 狀態 **A**:只能進行 **x** 操作,轉化到狀態 **B**
- 狀態 **A** 為初始狀態
- 狀態 **B**:有兩種操作:
- **x** 操作:轉化到狀態 **C**
- **y** 操作:轉化到狀態 **D**
- 狀態 **C**:有兩種操作
- **x** 操作:轉化到狀態 **D**
- **z** 操作:轉化到狀態 **A**
- 狀態 **D**:只能進行 **z** 操作,轉化到狀態 **C**
積分變化:
- 操作 **x** 會使角色增加 `100` 積分
- 操作 **y** 會使角色增加 `200` 積分
- 操作 **z** 會使角色減少 `50` 積分
### 3,編寫程式碼
下面我們使用狀態模式來編寫角色的狀態轉換程式。
首先根據狀態模式的類圖,我們需要有一個 **State** 介面,該介面包含角色所有的操作,並且包含一個狀態機的引用。
這裡我將 **State** 作為一個抽象類,每個操作的預設實現是 `do nothing`,每個具體狀態可以根據自己的需要進行覆蓋。
程式碼如下:
```java
abstract class State {
protected String stateName;
protected RoleStateMachine machine;
void x() {
// do nothing
}
void y() {
// do nothing
}
void z() {
// do nothing
}
// 獲取當前狀態名
public String getStateName() {
return stateName;
}
}
```
接下來編寫**角色狀態機**類,程式碼中也都寫了註釋:
```java
class RoleStateMachine {
private State currentState; // 當前狀態
private int score; // 積分
public RoleStateMachine() {
this.score = 0; // 初始積分為 0
// 初始狀態為 A
this.currentState = new StateA(this);
}
// 當發生某個操作時需要轉化到相應的狀態
// 用該方法進行設定
public void setCurrentState(State state) {
currentState = state;
}
// 獲取當前狀態
public String getCurrentState() {
return currentState.getStateName();
}
// 獲取積分
public int getScore() {
return score;
}
// 增加積分
public void addScore(int score) {
this.score += score;
}
// 減少積分
public void delScore(int score) {
this.score -= score;
}
// 狀態機中也包含狀態中的所有操作
// 每個操作都委託給當前狀態的相應操作來完成
public void x() {
currentState.x();
}
public void y() {
currentState.y();
}
public void z() {
currentState.z();
}
}
```
下面編寫 **4** 個狀態類,每個狀態類都繼承 **State** 介面,並且每個狀態類中要持有一個**狀態機的引用**,由建構函式引入:
```java
class StateA extends State {
public StateA(RoleStateMachine machine) {
this.machine = machine;
this.stateName = "StateA";
}
public void x() {
machine.addScore(100);
machine.setCurrentState(new StateB(machine));
}
}
class StateB extends State {
public StateB(RoleStateMachine machine) {
this.machine = machine;
this.stateName = "StateB";
}
public void x() {
machine.addScore(100);
machine.setCurrentState(new StateC(machine));
}
public void y() {
machine.addScore(200);
machine.setCurrentState(new StateD(machine));
}
}
class StateC extends State {
public StateC(RoleStateMachine machine) {
this.machine = machine;
this.stateName = "StateC";
}
public void x() {
machine.addScore(100);
machine.setCurrentState(new StateD(machine));
}
public void z() {
machine.delScore(50);
machine.setCurrentState(new StateA(machine));
}
}
class StateD extends State {
public StateD(RoleStateMachine machine) {
this.machine = machine;
this.stateName = "StateD";
}
public void z() {
machine.delScore(50);
machine.setCurrentState(new StateC(machine));
}
}
```
### 4,測試程式碼
下面來測試程式碼:
```java
RoleStateMachine role = new RoleStateMachine();
// 初始狀態為 StateA,積分為 0
assert role.getCurrentState().equals("StateA");
assert role.getScore() == 0;
role.y(); // 在狀態 A 進行 y 操作
// 在狀態 A 時,沒有 y 操作
// 所以如果進行 y 操作,狀態和積分都保持不變
assert role.getCurrentState().equals("StateA");
assert role.getScore() == 0;
role.x(); // 在狀態 A 進行 x 操作
assert role.getCurrentState().equals("StateB");
assert role.getScore() == 100;
role.y(); // 在狀態 B,進行 y 操作
assert role.getCurrentState().equals("StateD");
assert role.getScore() == 300;
role.z(); // 在狀態 D,進行 z 操作
assert role.getCurrentState().equals("StateC");
assert role.getScore() == 250;
role.z(); // 在狀態 C,進行 z 操作
assert role.getCurrentState().equals("StateA");
assert role.getScore() == 200;
System.out.println("Test OK.");
```
注意,使用 **Java assert** 時,記得用 `-ea` 引數開啟斷言功能。
我將完整的程式碼放在了[這裡](https://github.com/codeshellme/codeshellme.github.io/blob/master/somecode/dp/StatePattern.java),供大家參考。
### 5,總結
狀態模式將狀態和行為封裝成物件,不同的狀態有著不同的行為。這種設計使得處理**狀態轉換**這一類的邏輯變得非常有條理,而且不易出錯。
(本節完。)
---
**推薦閱讀:**
[命令模式-將請求封裝成物件](https://www.cnblogs.com/codeshell/p/14214705.html)
[介面卡模式-讓不相容的介面得以適配](https://www.cnblogs.com/codeshell/p/14228610.html)
[外觀模式-簡化子系統的複雜性](https://www.cnblogs.com/codeshell/p/14234062.html)
[模板方法模式-封裝一套演算法流程](https://www.cnblogs.com/codeshell/p/14239615.html)
[迭代器模式-統一集合的遍歷方式](https://www.cnblogs.com/codeshell/p/14244944.html)
---
*歡迎關注作者公眾號,獲取更多技術乾貨。*
![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic