用设计模式去掉没必要的状态变量 —— 状态模式
這是設計模式系列的第四篇,系列文章目錄如下:
一句話總結殊途同歸的設計模式:工廠模式=?策略模式=?模版方法模式
使用組合的設計模式 —— 美顏相機中的裝飾者模式
使用組合的設計模式 —— 追女孩要用的遠程代理模式
用設計模式去掉沒必要的狀態變量 —— 狀態模式
業務場景
這是在UI開發中經常會遇到的場景:界面有兩種狀態,每一種狀態下界面元素對應的操作都不同。比如在 offline 狀態下點擊大叉會直接退出應用,而在 login 狀態下點擊大叉會退出登錄。
最簡單直觀的方案就是用 int 值來保存當前狀態,根據 int 值不同會運行不同分支的操作。
方案一:狀態變量 + if-else
public class MainActivity extends AppCompatActivity {//'離線狀態'private static final int STATE_OFFLINE = 0;//'登陸狀態'private static final int STATE_LOGIN = 1;//'當前狀態'private int currentState = STATE_OFFLINE;//顯示狀態的控件private TextView tvState;//省略了設置布局文件和設置點擊監聽//'當按鈕點擊時執行的操作'public void onButtonClick() {if (currentState == STATE_OFFLINE) {logIn();setStateText("login");setState(STATE_LOGIN);}}//'當大叉被點擊時執行的操作'public void onCloseClick() {if (currentState == STATE_OFFLINE) {finish();} else if (currentState == STATE_LOGIN) {logOut();setStateText("offline");setState(STATE_OFFLINE);} }public void setStateText(String state) {tvState.setText(state);}//'設置當前狀態'public void setState(int state) {this.currentState = state;} } 復制代碼簡單直觀,狀態變量配合 if-else 就能實現需求。
新需要來了,新增群組功能,當登陸成功后,再次點擊登陸按鈕就能加入群組。在群組時點擊大叉會退出群組。
新需求增加了一種狀態,界面上的兩個操作按鈕也因此增加了兩種新的操作。
小場面,只需要新增 if-else 就能搞定:
public class MainActivity2 extends AppCompatActivity {private static final int STATE_OFFLINE = 0;private static final int STATE_LOGIN = 1;//'新增群組狀態'private static final int STATE_IN_GROUP = 2;private int currentState = STATE_OFFLINE;private TextView tvState;public void onButtonClick() {if (currentState == STATE_OFFLINE) {logIn();setStateText("login");setState(STATE_LOGIN);}//'按鈕新增對群組狀態的響應代碼'else if (currentState == STATE_LOGIN) {joinGroup();setStateText("in group");setState(STATE_IN_GROUP);}}public void onCloseClick() {if (currentState == STATE_OFFLINE) {finish();} else if (currentState == STATE_LOGIN) {logOut();setStateText("offline");setState(STATE_OFFLINE);} //'大叉新增對群組狀態的響應代碼'else if (currentState == STATE_IN_GROUP) {quitGroup();tvState.setText("login");setState(STATE_LOGIN);}} 復制代碼目前看起來還不是太糟,但隨著狀態的增加,if-else 分支就會原來越多,代碼可讀性會持續下降。
更關鍵的是這不符合開閉原則,即當新增功能的時候不允許修改原有代碼。而在 demo 中新增狀態的時候,不得不修改onCloseClick()和onButtonClick。demo 中的邏輯非常簡單,這兩個函數的調用者只有一個,分別是按鈕和大叉。真實項目中調用者可能分布在各個角落,對于這種函數,你敢輕易改嗎?一不小心就可能修改出 bug 。
如果需求變更:在離線狀態增加確認,即離線時點擊按鈕彈框確認是否需要登錄,點擊大叉彈框確認是否需要退出應用。如果使用上述方案,就需要全局搜索STATE_OFFLINE,找到所有訪問它的地方,一個個的做修改(可能散布在 n 個類中,增加了 n 個類出 bug 的可能性)。
吐槽完缺點后,看看狀態模式是怎么解決問題的。
方案二:狀態模式
在這個場景中,變化的是狀態,增加一層抽象把變化封裝起來是設計模式的慣用手段??聪氯绾伟褷顟B封裝起來:
public interface State {void onCloseClick();void onButtonClick(); } 復制代碼新增一層抽象,這層抽象的實例表示一個具體的狀態,抽象中的方法表示該狀態可以執行的操作。
現在有離線、登陸、進群組這三個狀態,分別對應著三個State實例:
//'離線狀態' public class OfflineState implements State {private MainActivity mainActivity;public OfflineState(MainActivity mainActivity) {this.mainActivity = mainActivity;}@Overridepublic void onCloseClick() {mainActivity.finish();}@Overridepublic void onButtonClick() {mainActivity.logIn();mainActivity.setState(mainActivity.getLoginState());mainActivity.setStateText("login");} }//'登陸狀態' public class LoginState implements State {private MainActivity mainActivity;public LoginState(MainActivity activity) {this.mainActivity = activity;}@Overridepublic void onCloseClick() {mainActivity.logOut();mainActivity.setState(mainActivity.getOfflineState());mainActivity.setStateText("offline");}@Overridepublic void onButtonClick() {mainActivity.joinGroup();mainActivity.setState(mainActivity.getInGroupState());mainActivity.setStateText("in group");} }//'進群組狀態' public class InGroupState implements State {private MainActivity mainActivity;public InGroupState(MainActivity mainActivity) {this.mainActivity = mainActivity;}@Overridepublic void onCloseClick() {mainActivity.quitGroup();mainActivity.setState(mainActivity.getLoginState());mainActivity.setStateText("login");}@Overridepublic void onButtonClick() {} } 復制代碼MainActivity頁面持有各個狀態的實例
public class MainActivity extends AppCompatActivity {//'離線狀態實例'private State offlineState;//'登陸狀態實例'private State loginState;//'進群組狀態實例'private State inGroupState;//'當前狀態'private State currentState;private TextView tvState;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//省略了布局和設置監聽器initState();}//'初始化狀態'private void initState() {offlineState = new OfflineState(this);loginState = new LoginState(this);inGroupState = new InGroupState(this);setStateText("offline");setState(offlineState);}//'將點擊按鈕操作委托給當前狀態'public void onButtonClick() {currentState.onButtonClick();}//'將點擊大叉操作委托給當前狀態'public void onCloseClick() {currentState.onCloseClick();}//'變更當前狀態'public void setState(State state) {this.currentState = state;}//'獲取指定狀態'public State getOfflineState() {return offlineState;}public State getLoginState() {return loginState;}public State getInGroupState() {return inGroupState;}public void setStateText(String state) {tvState.setText(state);} } 復制代碼這個方案的有趣之處在于:將“在每個方法內處理不同狀態” 轉變成 “在同一個狀態類內部實現所有方法”。怎么聽上去有種換湯不換藥的感覺?
其實不然,狀態模式在新增狀態時,讓原本的每一個狀態“對修改關閉”,讓MainActivity“對擴展開放”(因為新增狀態不要修改onCloseClick()和onButtonClick())
又是一個“把變的東西封裝起來,用多態來應對變化”的設計模式。(它和工廠模式,模版方法模式,策略模式殊途同歸,詳見設計模式第一篇)
狀態模式 vs 策略模式
分析設計模式總是逃不掉相互比較,因為有幾個長的真的很像。策略模式的詳細講解和應用可以分別移步這里和這里
它們倆的實現方式和目的可以說幾乎相同,都是通過接口定義行為,通過組合持有行為實例,通過多態動態地替換行為。
但它們的適用場景略有區別:策略模式是在外部定義了一個行為,并由外部發起一次性的行為替換,而狀態模式在內部定義了多個行為,并由內部原因持續地發生行為替換。
轉載于:https://juejin.im/post/5d004621f265da1bc37f0674
總結
以上是生活随笔為你收集整理的用设计模式去掉没必要的状态变量 —— 状态模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海康摄像机网口接线方法 海康摄像头的线怎
- 下一篇: [windows]windows 10