这是设计模式系列的第四篇,系列文章目录以下:设计模式
这是在UI开发中常常会遇到的场景:界面有两种状态,每一种状态下界面元素对应的操做都不一样。好比在 offline 状态下点击大叉会直接退出应用,而在 login 状态下点击大叉会退出登陆。post
最简单直观的方案就是用 int 值来保存当前状态,根据 int 值不一样会运行不一样分支的操做。ui
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 就能实现需求。this
新须要来了,新增群组功能,当登录成功后,再次点击登录按钮就能加入群组。在群组时点击大叉会退出群组。spa
新需求增长了一种状态,界面上的两个操做按钮也所以增长了两种新的操做。设计
小场面,只须要新增 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 的可能性)。
吐槽完缺点后,看看状态模式
是怎么解决问题的。
在这个场景中,变化的是状态,增长一层抽象把变化封装起来是设计模式的惯用手段。看下如何把状态封装起来:
public interface State {
void onCloseClick();
void onButtonClick();
}
复制代码
新增一层抽象,这层抽象的实例表示一个具体的状态,抽象中的方法表示该状态能够执行的操做。
如今有离线、登录、进群组这三个状态,分别对应着三个State
实例:
//'离线状态'
public class OfflineState implements State {
private MainActivity mainActivity;
public OfflineState(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
@Override
public void onCloseClick() {
mainActivity.finish();
}
@Override
public 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;
}
@Override
public void onCloseClick() {
mainActivity.logOut();
mainActivity.setState(mainActivity.getOfflineState());
mainActivity.setStateText("offline");
}
@Override
public 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;
}
@Override
public void onCloseClick() {
mainActivity.quitGroup();
mainActivity.setState(mainActivity.getLoginState());
mainActivity.setStateText("login");
}
@Override
public void onButtonClick() {}
}
复制代码
MainActivity
页面持有各个状态的实例
public class MainActivity extends AppCompatActivity {
//'离线状态实例'
private State offlineState;
//'登录状态实例'
private State loginState;
//'进群组状态实例'
private State inGroupState;
//'当前状态'
private State currentState;
private TextView tvState;
@Override
protected 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()
)
又是一个“把变的东西封装起来,用多态来应对变化”的设计模式。(它和工厂模式,模版方法模式,策略模式异曲同工,详见设计模式第一篇)
分析设计模式老是逃不掉相互比较,由于有几个长的真的很像。策略模式的详细讲解和应用能够分别移步这里和这里
它们俩的实现方式和目的能够说几乎相同,都是经过接口定义行为,经过组合持有行为实例,经过多态动态地替换行为。
但它们的适用场景略有区别:策略模式是在外部定义了一个行为,并由外部发起一次性的行为替换,而状态模式在内部定义了多个行为,并由内部缘由持续地发生行为替换。