(本文由言念小文原创,转载请注明出处)设计模式
在实际工做中常常遇到某个对象,处于不一样的状态有不一样行为逻辑、且状态之间能够相互迁移的业务场景,特别是在开发通讯协议栈类软件中尤其多见。《设计模式之禅》这本书中对状态模式有着很是详尽的讲解(目前为止我认为讲解得最好的书),但总以为本身没可以理解透彻、灵活运用。直到今年完成了一个通讯协议软件的开发,从新研究了“状态机”,而后回过头来理解当初学习的状态模式,豁然开朗。所以,本文先从状态机开始讲解,而后结合状态机详细阐述状态模式的两种实现方式,最后给出状态模式的优缺点及其使用场景。架构
一 案例描述app
按照老风格,本文先描述一个场景案例,而后围绕案例来展开后文。相信每一个人都用过手机的应用商城,一般在应用商城中会将能够安装的app以列表(listview)的形式呈现,一个应用占据列表的一个子项(item),以下图1所示:ide
图1学习
咱们将注意力聚焦到item的按钮上:this
a当检测到可安装的app,按钮显示“安装”;编码
b点击按钮,软件会去下载app安装包,这时按钮更新视图,显示“正在下载”(即安装进度);spa
c下载完成后,软件自动安装app,按钮显示“正在安装”;设计
d安装完成后,按钮显示“打开”,这时点击按钮将打开对应的app。3d
一般,一切顺利,咱们安装一个app,按钮会经历“安装”“正在下载”“正在安装”“打开”四种状态。惋惜的是,每每事有多磨:
当下载app安装包时,可能出现下载异常,这时按钮切换状态到“下载失败”,点击按钮,软件从新尝试下载,按钮切换状态到“正在下载”;
当安装app时,可能出现安装失败,这时按钮切换状态到“安装失败”,点击按钮,软件从新尝试安装,按钮切换状态到“正在安装”;
以上,即是咱们更新一个软件时,可能遇到状况。后文,咱们将实现上述功能的软件模块称为“app安装模块”,本文将以这个案例为基础,围绕实现“app安装模块”展开状态机和状态模式的讲解。
二 状态机
1.什么是状态机
一般咱们工做中接触到的状态机都是有限状态机,那么什么是有限状态机呢?偷个懒直接百度大挪移:
有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动做等行为的数学模型。
注意这个定义里面两个关键字“有限状态”“自动机”。“有限状态”指明状态机中的状态是有限且明确的,在案例中button的状态有:待安装、正在下载、下载失败、正在安装、安装失败、待打开。“自动机”说明状态机中:状态及状态下对应动做是在状态机内部自动转换和执行的,调用状态机的客户端,无需关心状态机内部的状态迁移和动做执行。
2.1 状态机的构成要素
状态机由如下四大要素构成:
现态(Qn) -- 当前状态机所处的状态。
次态(Qn+1) -- 状态机要迁移到的新状态。
事件(EVENT)(又称为条件) -- 状态机的触发信号;事件到来,可以触发状态机执行特定动做,或进行状态迁移,或两者皆执行;事件通常来自于状态机外部。
动做(ACTION) -- 事件到来后,状态机执行的动做,动做执行完后,状态机可迁移到新状态也可维持原状态,故而对于状态机中的某一状态,动做并不是必须。
2.2 状态机的描述方式
状态机的描述方式有两种:状态迁移图和状态机表。
状态迁移图:
状态迁移图经过图形的方式来描述对象的所有状态逻辑,这种方式比较直观、清晰。状态迁移图由状态、状态迁移、事件和动做构成。其中,事件和动做写在状态迁移的带箭头线条上,如图2所示,图2为“app安装模块”状态迁移图,圆圈和双圆圈表示起始和结束状态。
图2 “app安装”状态迁移图
状态机表:
状态迁移表经过矩阵的方式,描述状态机的状态迁移与行为逻辑。状态机表有两种写法:
第一种,横竖表头都为状态,横表头为现态,竖表头为次态,现态和次态相交的单元格为事件触发后要执行的动做,如表1所示:
现态 次态 |
待安装 |
下载中 |
下载失败 |
安装中 |
安装失败 |
待打开 |
待安装 |
- |
- |
- |
- |
- |
- |
下载中 |
download() |
- |
download() |
- |
- |
- |
下载失败 |
- |
undo() |
- |
- |
- |
- |
安装中 |
install() |
install() |
- |
- |
install() |
- |
安装失败 |
- |
- |
- |
undo() |
- |
- |
待打开 |
- |
- |
- |
undo() |
- |
open() |
表1
注意1:途中undo()为表示只作状态转移,实际不执行其余动做。
注意2:因为不论什么状态,只要状态迁移了,都会有UI上变化,所以更新UI的动做updateView()不重复的提现啊状态机图和状态机表中。
第二种,横表头为现态,竖表头为事件触发后的动做,现态和动做相交的单元格,为次态。
现态 动做 |
待安装 |
下载中 |
下载失败 |
安装中 |
安装失败 |
待打开 |
download() |
下载中 |
- |
下载中 |
- |
- |
- |
install() |
安装中 |
安装中 |
- |
- |
安装中 |
- |
open() |
- |
- |
- |
待打开 |
- |
待打开 |
表2
两种状态机表各有特色,第一种比较适合状态较多的状况,第二种适合动做比较多的状况(根据小文的我的工程经验,比较推荐第二种)。从状态表中能够看到,状态表不能很好的描述出单个状态和动做的触发事件,所以一般状态表仍是须要和状态迁移图结合使用的。
2.1 状态机的运行过程
状态机的运行实际是状态的迁移和对应动做的执行,这里我总结以下的运行分支:
EVENT-->ACTION // 事件触发,只执行动做,不转移状态
EVENT-->TRANS STATE // 事件触发,只转移状态,不执行动做
EVENT-->ACTION-->TRANS STATE // 事件触发,先作动做,后转移状态
须要说明的是:这里的ACTION一般都是触发状态转移必需要作的动做,若是不作,状态将没法成功迁移。好比,案例“app安装模块”从“安装”到“正在下载”状态的迁移,状态迁移前必需要执行download()动做,若是没有执行这个动做,状态是没法成功迁移的。
有人可能会有疑问,能够按照EVENT-->TRANS STATE-->ACTION运行吗,其实在实际的编码过程当中,是能够的:案例中“app安装模块”先从“安装”迁移到“正在下载”状态,紧接着在“正在下载”状态下执行download()动做,这样在功能实现上与前一种运行顺序没有差别。不过,我我的更喜欢EVENT-->ACTION-->TRANS STATE这种顺序,由于这种顺序更加符合咱们的天然逻辑。
三 状态模式
1 为何要使用状态模式?
1.1 什么是状态模式?
在此,我不想套用GOF的定义,由于定义每每是总结和归纳后高度提炼的概念,不太利于理解。当咱们在项目开发过程当中,分析某些业务对象或模块,发现他们的运行规律表现为状态机特征的时候,状态模式可能就要提上咱们架构方案了。那么到底什么是状态模式呢?别急,看完后面的文章,相信你本身能总结出来。
1.2 为何要使用状态模式
咱们使用状态模式就是为了用软件实现具备状态机特征的业务对象,为何要这样作呢?在状态机的定义一节,咱们讲到“状态及状态下对应动做是在状态机内部自动转换和执行的,调用状态机的客户端,无需关心状态机内部的状态迁移和动做执行”。所以,状态模式是一种高度封装、高度解耦的、易于拓展的架构模式。这么好的模式,固然要啦。
2 使用状态模式完成案例
咱们先来分解一下状态模式要达到的目标:a.状态及状态下对应动做是在状态机内部自动转换和执行的;b.调用状态机的客户端,无需关心状态机内部的状态迁移和动做执行。
要达到目标a,那么咱们每种具体状态必需要进行封装:状态内部的动做和转换,是要封装在这个状态内部的,每一个状态都必须至少要将如下两个要素封装其中:动做、状态转移方法。
要实现目标b,具体的各类状态就不能直接暴露给调用的客户端(Client),从Client到各具体状态(ConcreteState),中间必需要有一个对象,对各状态进行统一管理和无差异的暴露给Client,Client只须要与这个对象交互,就能触发软件模块自动正确运行。
2.1静态类图
经过目标分解,而后反向推理分析,即可以直接给出静态类图方案:
Client:调用“app安装模块”的客户端。
StateContext:状态上下文,即状态的环境类,对各个具体状态进行封装和管理,让各个具体状态无差异的曝露给客户端,StateContext曝露给客户端的永远是当前的状态。
State:抽象状态。
ConcreteState:具体状态。
2.2状态模式实现代码
2.2.1方式1
方式1按照咱们常规的天然逻辑,在各个状态中按照EVENT-->ACTION-->TRANS STATE顺序运行。
第一步 定义抽象State
public abstract class State { public StateContext stateContext; public void setStateContext(StateContext context){ stateContext = context; } protected void updateView(String s) { System.out.println("update button view = " + s); }; protected abstract void doAction(Event e); protected abstract void transState(Event e); public abstract void eventChange(Event e); }
对于状态,必需要有事件触发、执行动做、状态转移几种方法,结合本案例,还要有更新UI的方法,此外一个状态必需要持有状态环境对象stateContext,才能在状态迁移的时候,更新stateContext中的当前状态。
第二步 定义StateContext
public class StateContext { // 当前状态 public State currState; // 定义出全部状态 public static final StateToInstall stateToInstall = new StateToInstall(); public static final StateDownloading stateDownloading = new StateDownloading(); public static final StateDownloadFailed stateDownloadFailed = new StateDownloadFailed(); public static final StateInstalling stateInstalling = new StateInstalling(); public static final StateInstallFailed stateInstallFailed = new StateInstallFailed(); public static final StateToOpen stateToOpen = new StateToOpen(); public StateContext(State state) { currState = state; // context对象传递给当前状态对象 this.currState.setStateContext(this); } /** * 获取当前状态 * @return 当前状态 */ public State getCurrState() { return currState; } /** * 设置当前状态 * @param currState */ public void setCurrState(State currState) { this.currState = currState; // context对象传递给当前状态对象 this.currState.setStateContext(this); } /** * 触发条件改变 * @param e */ public void eventChange(Event e) { currState.eventChange(e); } }
注意:StateContext与State之间是聚合关系,故而在StateContext中定义出全部具体状态。
第三步 定义事件类型
这里用一个枚举类来定义触发“app安装模块”动做执行和状态迁移的事件信号
public enum Event { EVENT_CLICK, // 按钮点击 EVENT_DOWNLOAD_FAILED, // 下载失败 EVENT_DOWNLOAD_SUCCESS, // 如今成功 EVENT_INSTALL_FAILED, // 安装失败 EVENT_INSTALL_SUCCESS, // 安装成功 }
第四步 定义具体状态类
/** * “待安装”状态类 * @author 言念小文 * */ public class StateToInstall extends State{ @Override protected void doAction(Event e) { if(Event.EVENT_CLICK.equals(e) && !checkDownloaded()) { System.out.println("current state = StateToInstall, " + "event change signal = click button, " + "do action download()"); updateView("下载中"); return; } if(Event.EVENT_CLICK.equals(e) && checkDownloaded()) { System.out.println("current state = StateToInstall, " + "event change signal = click button, " + "do action install()"); updateView("安装中"); return; } } @Override protected void transState(Event e) { if(Event.EVENT_CLICK.equals(e) && !checkDownloaded()) { System.out.println("current state = StateToInstall, " + "event change signal = click button, " + "transfer state to StateDownloading"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateDownloading); return; } if(Event.EVENT_CLICK.equals(e) && checkDownloaded()) { System.out.println("current state = StateToInstall, " + "event change signal = click button, " + "transfer state to StateInstalling"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateInstalling); return; } } @Override public void eventChange(Event e) { if(!Event.EVENT_CLICK.equals(e)) { return; } // 执行动做 doAction(e); // 转移状态 transState(e); } private boolean checkDownloaded() { return false; } }
在具体类中实现doAction(Event e)、transState(Event e)、eventChange(Event e)方法,具体类持有环境对象StateContext的实例。当外部事件信号经过StateContext传入某个具体类中,StateContext调用具体类中的eventChange(Event e)方法,eventChange(Event e)方法经过调用doAction()和transState()来实现动做的执行和状态的转移,这样具体类就将本状态执行的动做和状态迁移所有封装在具体状态类中,Client只须要调用StateContext实例,而无需关心具体的状态类。
/** * “下载中”状态类 * @author 言念小文 * */ public class StateDownloading extends State{ @Override protected void doAction(Event e) { // 不管是下载成功或失败,无需执行其余动做,紧更新view if(Event.EVENT_DOWNLOAD_FAILED.equals(e)) { System.out.println("current state = StateDownloading, " + "event change signal = download failed, " + "do action nothing"); updateView("下载失败"); return; } if(Event.EVENT_DOWNLOAD_SUCCESS.equals(e)) { System.out.println("current state = StateDownloading, " + "event change signal = download success, " + "do action install()"); updateView("安装中"); return; } } @Override protected void transState(Event e) { if(Event.EVENT_DOWNLOAD_FAILED.equals(e)) { System.out.println("current state = StateToInstall, " + "event change signal = click button, " + "transfer state to StateDownloadFailed"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateDownloadFailed); return; } if(Event.EVENT_DOWNLOAD_SUCCESS.equals(e)) { System.out.println("current state = StateToInstall, " + "event change signal = click button, " + "transfer state to StateInstalling"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateInstalling); return; } } @Override public void eventChange(Event e) { if(!Event.EVENT_DOWNLOAD_FAILED.equals(e) && !Event.EVENT_DOWNLOAD_SUCCESS.equals(e)) { return; } // 执行动做 doAction(e); // 转移状态 transState(e); } } /** * “下载失败”状态类 * @author 言念小文 * */ public class StateDownloadFailed extends State{ @Override protected void doAction(Event e) { if(Event.EVENT_CLICK.equals(e)) { System.out.println("current state = StateDownloadFailed, " + "event change signal = click button, " + "do action download()"); updateView("下载中"); return; } } @Override protected void transState(Event e) { if(Event.EVENT_CLICK.equals(e)) { System.out.println("current state = StateDownloadFailed, " + "event change signal = click button, " + "transfer state to StateDownloading"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateDownloading); return; } } @Override public void eventChange(Event e) { if(!Event.EVENT_CLICK.equals(e)) { return; } // 执行动做 doAction(e); // 转移状态 transState(e); } } /** * “安装中”状态类 * @author 言念小文 * */ public class StateInstalling extends State{ @Override protected void doAction(Event e) { if(Event.EVENT_INSTALL_FAILED.equals(e)) { System.out.println("current state = StateInstalling, " + "event change signal = install failed, " + "do action nothing"); updateView("安装失败"); return; } if(Event.EVENT_INSTALL_SUCCESS.equals(e)) { System.out.println("current state = StateInstalling, " + "event change signal = install success, " + "do action nothing"); updateView("打开"); return; } } @Override protected void transState(Event e) { if(Event.EVENT_INSTALL_FAILED.equals(e)) { System.out.println("current state = StateInstalling, " + "event change signal = install failed, " + "transfer state to StateInstallFailed"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateInstallFailed); return; } if(Event.EVENT_INSTALL_SUCCESS.equals(e)) { System.out.println("current state = StateInstalling, " + "event change signal = install success, " + "transfer state to StateToOpen"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateToOpen); return; } } @Override public void eventChange(Event e) { if(!Event.EVENT_INSTALL_FAILED.equals(e) && !Event.EVENT_INSTALL_SUCCESS.equals(e)) { return; } // 执行动做 doAction(e); // 转移状态 transState(e); } } /** * “安装失败”状态类 * @author 言念小文 * */ public class StateInstallFailed extends State{ @Override protected void doAction(Event e) { if(Event.EVENT_CLICK.equals(e)) { System.out.println("current state = StateInstallFailed, " + "event change signal = click button, " + "do action install()"); updateView("安装中"); return; } } @Override protected void transState(Event e) { if(Event.EVENT_CLICK.equals(e)) { System.out.println("current state = StateInstallFailed, " + "event change signal = click button, " + "transfer state to StateInstalling"); // 状态转移后,设置状态环境类当前状态 stateContext.setCurrState(StateContext.stateInstalling); return; } } @Override public void eventChange(Event e) { if(!Event.EVENT_CLICK.equals(e)) { return; } // 执行动做 doAction(e); // 转移状态 transState(e); } } /** * “待打开”状态类 * @author 言念小文 * */ public class StateToOpen extends State{ @Override protected void doAction(Event e) { if(Event.EVENT_CLICK.equals(e)) { System.out.println("current state = StateToOpen, " + "event change signal = click button, " + "do action open()"); // 点击打开,button view没有变化 updateView("打开"); return; } } @Override protected void transState(Event e) { if(Event.EVENT_CLICK.equals(e)) { // 状态不发生转移 stateContext.setCurrState(StateContext.stateToOpen); return; } } @Override public void eventChange(Event e) { if(!Event.EVENT_CLICK.equals(e)) { return; } // 执行动做 doAction(e); // 转移状态 transState(e); } }
第五步 定义Client,并运行程序
public class Client { public static void main(String[] args) { // 建立状态环境类对象,并初始化状态 StateContext context = new StateContext(StateContext.stateToInstall); // 下载 context.eventChange(Event.EVENT_CLICK); System.out.println("-----------------------------------------------\r"); // 下载失败 context.eventChange(Event.EVENT_DOWNLOAD_FAILED); System.out.println("-----------------------------------------------\r"); // 从新下载 context.eventChange(Event.EVENT_CLICK); System.out.println("-----------------------------------------------\r"); // 下载成功 context.eventChange(Event.EVENT_DOWNLOAD_SUCCESS); System.out.println("-----------------------------------------------\r"); // 安装失败 context.eventChange(Event.EVENT_INSTALL_FAILED); System.out.println("-----------------------------------------------\r"); // 从新安装 context.eventChange(Event.EVENT_CLICK); System.out.println("-----------------------------------------------\r"); // 安装成功 context.eventChange(Event.EVENT_INSTALL_SUCCESS); System.out.println("-----------------------------------------------\r"); } }
Client类中,只须要持有StateContext,而后输入不一样的事件件号,就能够出发“app安装模块”的动做执行和状态迁移。
执行结果以下:
current state = StateToInstall, event change signal = click button, do action download()
update button view = 下载中
current state = StateToInstall, event change signal = click button, transfer state to StateDownloading
-----------------------------------------------
current state = StateDownloading, event change signal = download failed, do action nothing
update button view = 下载失败
current state = StateToInstall, event change signal = click button, transfer state to StateDownloadFailed
-----------------------------------------------
current state = StateDownloadFailed, event change signal = click button, do action download()
update button view = 下载中
current state = StateDownloadFailed, event change signal = click button, transfer state to StateDownloading
-----------------------------------------------
current state = StateDownloading, event change signal = download success, do action install()
update button view = 安装中
current state = StateToInstall, event change signal = click button, transfer state to StateInstalling
-----------------------------------------------
current state = StateInstalling, event change signal = install failed, do action nothing
update button view = 安装失败
current state = StateInstalling, event change signal = install failed, transfer state to StateInstallFailed
-----------------------------------------------
current state = StateInstallFailed, event change signal = click button, do action install()
update button view = 安装中
current state = StateInstallFailed, event change signal = click button, transfer state to StateInstalling
-----------------------------------------------
current state = StateInstalling, event change signal = install success, do action nothing
update button view = 打开
current state = StateInstalling, event change signal = install success, transfer state to StateToOpen
-----------------------------------------------
2.2.2方式2
前文咱们说了,在实际编码的过程当中,EVENT-->TRANS STATE-->ACTION执行顺序也是能够的的,只不过须要将Action定义在次态中,而后次态中的Action要委托到现态中执行。具体的编码方式请参照《设计模式之禅道》关于状态模式的章节。
四 状态模式优缺点及使用场景
1 状态模式优缺点
优势:从前文咱们已经能够看出,Client只须要持有StateContext实例,仅仅经过事件信号就能够驱动“app安装模块”的运行,无需关注软件模块内部实现,故具备很好的封装性,能很好解耦。若是要添加一种新状态,只须要添加一个新的状态子类,无需影响其余类,故而扩展性良好。
缺点:随着状态增长,状态子类会增多,致使类膨胀。
2 应用场景
我的认为,对于某个模块或者对象,其行为出现状态机特征的都可以使用该模式,以达到解耦、高扩展性、避免过多条件分支语句的目的。