簡介
狀態模式主要解決的是當控制一個物件狀態轉換的條件表示式過於複雜時的情況。把狀態的判斷邏輯轉移到表示不同狀態的一系列類當中,可以把複雜的判斷邏輯簡化.
一個複雜的業務中可能存在大量的 if else等邏輯條件判斷,對於後期維護來說是非常危險和複雜的。而狀態模式也是將與特定狀態相關的行為區域性化,並且將不同狀態的行為分割開來.
可以消除大量的條件分支語句,內部通過狀態轉移,來減少之間的相互依賴
- 思考: 策略模式、責任鏈模式和狀態模式的區別是什麼
場景設定
比如你在參與百度網盤開發,有以下常見場景:
會員等級 | 許可權 |
---|---|
普通使用者 | 儲存照片、檔案 |
會員 | 極速下載、5T空間... |
超級會員 | 小視訊自動備份、音視訊倍速播放... |
簡單實現
許可權獲取類
package design.pattern;
import java.util.ArrayList;
public class UserRule {
/**
* 等級 1普通使用者 2會員 3超級會員
*/
private Integer level = 1;
/**
* 許可權容器
*/
private ArrayList<String> ruleList = new ArrayList<String>() {
{
add("上傳檔案");
add("下載檔案");
}
};
public UserRule(Integer level) {
this.level = level;
}
/**
* 獲取許可權列表
*
* @return
*/
public ArrayList<String> getRuleList() {
if (this.level == 2) { //會員
//todo 許可權獲取
ruleList.add("極速下載");
ruleList.add("5T空間");
} else if (this.level == 3) { //超級會員
//todo 許可權獲取
ruleList.add("光速下載");
ruleList.add("10T空間");
ruleList.add("小視訊自動備份");
ruleList.add("音視訊倍速播放");
}
return ruleList;
}
}
複製程式碼
客戶端呼叫:
Integer requestLevel = 3;
UserRule userRule = new UserRule(requestLevel);
ArrayList<String> ruleList = userRule.getRuleList();
//列印許可權
System.out.println("會員等級" + requestLevel + "許可權列表:");
for (Object object : ruleList) {
System.out.println(object);
}
複製程式碼
output:
會員等級3許可權列表:
上傳檔案
下載檔案
光速下載
10T空間
小視訊自動備份
音視訊倍速播放
複製程式碼
思考:
似乎看起來我們的程式碼足夠簡單,很好的滿足了根據等級返回許可權的需求
我們重點關注下獲取許可權列表的根據不同的條件分支,處理不同的todo 的業務邏輯,如果我們加入了更多的等級,更復雜的許可權計算方式等等功能, 這個if else將會更加的龐大起來.
剛剛說完可能龐大起來,產品過來又給我提v2.0的需求.
- 產品: 最近市場反饋說我們會員賣的不是很好,現在我們要加一點點功能。
- 我: ...
- 產品: 就是再返回當前等級的時候,把下個等級將要獲取到的許可權資訊也給他返回,讓他看到,鼓勵使用者做任務或付費.
- 我: 我們不是有個列表可以讓他直接對比看麼,為什麼還要單獨提示啊
- 產品: 你是產品還是我是產品啊
- 我: 心裡默唸《人人都是產品經理》
使用狀態模式訊息掉那些多餘的if else (當然還有那個最新的需求)
我們發現如果再加上返回下個版本的許可權,真的是夠了,再過幾天不一定又出什麼需求,這個方法看起來都要崩潰了. 看來需要優化一下了。
首先我們建立一個抽象類(核心作用方便子類約束和傳遞)
State.java
package design.pattern.Rules;
import design.pattern.UserRule;
import design.pattern.UserVo;
import java.util.ArrayList;
public abstract class State {
/**
* 使用者物件
*/
protected UserRule userRule;
/**
* 許可權容器
*/
protected ArrayList<String> ruleList = new ArrayList<String>() {
{
add("上傳檔案");
add("下載檔案");
}
};
public State(UserRule userRule) {
this.userRule = userRule;
}
public abstract ArrayList<String> getRuleList(UserVo userVo);
}
複製程式碼
第一步我們需要簡單的引數物件(這裡用view object)
UserVo.java
package design.pattern;
public class UserVo {
private String name;
private Integer level;
public UserVo(String name, Integer level) {
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public Integer getLevel() {
return level;
}
}
複製程式碼
繼承State.java狀態類,實現各自的會員返回類
package design.pattern.Rules;
import design.pattern.UserRule;
import design.pattern.UserVo;
import java.util.ArrayList;
/**
* 一類會員
*/
public class MemberOne extends State {
public MemberOne(UserRule userRule) {
super(userRule);
}
/**
* 獲取許可權列表
*
* @return
*/
public ArrayList<String> getRuleList(UserVo userVo) {
//如果是一類會員(普通)
if (userVo.getLevel() == 1) {
return ruleList;
} else {
userRule.setState(new MemberTwo(userRule)); //設定下一級別類
return userRule.getRuleList(userVo); //獲取下一個級別的詳情
}
}
}
複製程式碼
UserRule.java (橋樑類)
package design.pattern;
import design.pattern.Rules.MemberOne;
import design.pattern.Rules.State;
import java.util.ArrayList;
public class UserRule {
/**
* 具體許可權物件
*/
private State currentRule;
public UserRule() {
currentRule = new MemberOne(this);
}
/**
* 設定許可權物件
*
* @param state
*/
public void setState(State state) {
this.currentRule = state;
}
public ArrayList<String> getRuleList(UserVo userVo) {
return this.currentRule.getRuleList(userVo);
}
}
複製程式碼
userRule類為我們優化前充滿了條件判斷的類,對他進行了解耦合.可以理解為對內呼叫類,對外暴露類的橋樑類
userVo物件是我們的引數物件,這裡主要用於等級判斷. 如果不成立,則進行重新設定下一個處理規則類,並同樣呼叫規則列表方法。
剩下的兩個會員類
package design.pattern.Rules;
import design.pattern.UserRule;
import design.pattern.UserVo;
import java.util.ArrayList;
/**
* 三類會員
*/
public class MemberTwo extends State {
public MemberTwo(UserRule userRule) {
super(userRule);
}
/**
* 獲取許可權列表
*
* @return
*/
public ArrayList<String> getRuleList(UserVo userVo) {
if (userVo.getLevel() == 2) {
ruleList.add("極速下載");
ruleList.add("5T空間");
return ruleList;
}else{
userRule.setState(new MemberThree(userRule));
return userRule.getRuleList(userVo);
}
}
}
複製程式碼
package design.pattern.Rules;
import design.pattern.UserRule;
import design.pattern.UserVo;
import java.util.ArrayList;
/**
* 三類會員
*/
public class MemberThree extends State {
public MemberThree(UserRule userRule) {
super(userRule);
}
/**
* 獲取許可權列表
*
* @return
*/
public ArrayList<String> getRuleList(UserVo userVo) {
//最高階
ruleList.add("光速下載");
ruleList.add("10T空間");
ruleList.add("小視訊自動備份");
ruleList.add("音視訊倍速播放");
return ruleList;
}
}
複製程式碼
為了引數方便傳遞管理,我們單獨使用一個view object類.
UserVo.java
package design.pattern;
public class UserVo {
private String name;
private Integer level;
public UserVo(String name, Integer level) {
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public Integer getLevel() {
return level;
}
public void setName(String name) {
this.name = name;
}
public void setLevel(int level) {
this.level = level;
}
}
複製程式碼
客戶端呼叫:
UserVo userVo = new UserVo("小紅", 1);
UserRule userRule = new UserRule();
ArrayList<String> ruleList = userRule.getRuleList(userVo);
//列印
System.out.println("使用者" + userVo.getName() + "當前許可權如下:");
for (Object object : ruleList) {
System.out.println(object);
}
//他的下個許可權可以獲得
userVo.setLevel(userVo.getLevel() + 1);
ruleList = userRule.getRuleList(userVo);
System.out.println("使用者" + userVo.getName() + "將要許可權如下:");
for (Object object : ruleList) {
System.out.println(object);
}
複製程式碼
output:
使用者小紅當前許可權如下:
上傳檔案
下載檔案
使用者小紅將要許可權如下:
上傳檔案
下載檔案
極速下載
5T空間
複製程式碼
核心程式碼:
if (userVo.getLevel() == 1) {
return ruleList;
} else {
userRule.setState(new MemberTwo(userRule)); //設定下一級別類
return userRule.getRuleList(userVo); //獲取下一個級別的詳情
}
複製程式碼
思考這段程式碼存在的問題? 如何優化?
狀態模式UML圖和策略模式一樣
策略模式和狀態模式區別在哪裡?
策略模式與狀態模式極其相似,但是二者有其內在的差別
- 策略模式將具體策略類暴露出去,呼叫者需要具體明白每個策略的不同之處以便正確使用。
- 狀態模式狀態的改變是由其內部條件來改變的,與外界無關,二者在思想上有本質區別.
userRule.setState(new MemberTwo(userRule));),
複製程式碼
對比策略模式: 策略模式詳解
更多精彩內容請關注熱情小宇公眾號(呆呆熊一點通)