说到状态模式,如果你看过之前发布的重构系列的文章中的《代码重构(六):代码重构完整案例》这篇博客的话,那么你应该对“状态模式”并不陌生,因为我们之前使用到了状态模式进行重构。上一篇博客我们讲的主题是“组合模式”,我们使用组合模式创建了一个树形结构,并给出了遍历方式。今天我们来认识一下另一种模式,那就是“状态模式”,今天就从银行的ATM自动取款机中的取款流程来学习一下状态模式。

还是老规矩,开门见山。下方是状态模式的定义:

状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修够了它的类。

其实状态模式与策略模式的共同点非常之多,可以说状态模式是策略模式的升级版本。关于策略模式的内容,请参加之前关于策略模式的博客《设计模式(一):“穿越火线”中的“策略模式”(Strategy Pattern)》。今天我们就从取款机中的各种状态,以及各种状态间的切换来学习一下状态模式。我们先给出没有状态模式的实现方案,然后根据其产生的问题类使用状态模式进行重构。废话少说,进入今天的主题。

一、无“状态模式”的ATM

该部分给出了无“状态模式”的ATM机的具体实现,该部分先对各种状态已经各种状态间的转换进行分析,然后给出个个状态之间的关系,有点状态图的意思。然后在根据此状态图来实现我们的代码,当然虽然是根据状态图实现的代码,在该部分我们没有使用状态模式。所有的状态转换我们都在一个ATM的类中进行的。该部分就给出了具体实现。

1.ATM机状态分析

首先我们先对ATM机的各种状态,下方就是我们所画出的类“状态图”。每个方框就是一种状态,而我们的ATM机大致分为无卡,有卡,解密,取款,取出金额,余额不足这六种状态,也就是下方“状态图”中方框中的内容。然后就是动作了,每种状态间的转化我们需要动作才可以完成。在我们的ATM机中大致分为插卡,退卡,输入密码,输入金额,确认取款这几种动作。状态间转换时所需的动作关系如下所示。因为该部分实现的代码位于一个类中,再扯就不做过多的赘述了。

2. 上述状态关系的具体实现

在代码实现时,首先我们使用枚举来列举出所有的状态,此处我们命名为ATMState。下方代码段就是我们ATM机所有的状态,如下所示:

给出状态枚举后,接着我们要实现ATM机的类,下方就是我们ATM机的类。state成员变量就记录了当前ATM机所处的状态,默认是无卡状态。money成员变量记录了当前取款机中银行卡的余额,余额默认是0。inputMoney存储了用户想提取金额,默认值也是0。insertBankCard()方法则表示插入银行卡的动作,在执行该动作时,根据ATM机当前所处的状态来决定要做哪些事情。比如当前已经处于有卡的状态(HasBankCardState),则会提示“目前已有银行卡,可以输入密码进行取款”,如处于无卡状态(NoBankCardState),则可以插入银行卡,并将状态改为有卡状态(HasBankCardState),具体请看下方该方法的实现。backBankCard()方法则代表着退卡的动作,在该方法的实现方式与insertBankCard()方法类似,也是根据不同的ATM状态,执行不同的事情,具体实现如下所示。inputPassword()则表示输入密码的动作,inputMoney(money)则代表着输入取款金额的动作。tapOkButton()方法则代表着点击取款按钮的动作。

这些动作的实现方式都差不多,都是根据当前ATM机的状态来执行一些东西。如果所做的事情会改变ATM机的状态(比如插入卡),那么在执行完动作后就立即改变ATM机的状态。下方给出了两个方法的实现,其他的方法请参考Github分享的完整实例,分享地址见博文结尾部分。

3、测试用例

上面我们给出了ATM机的实现,接下来就是到了测试的时候了,也就是ATM机我们造完了,接下来该使用了。下方就是我们的测试用例,该用例稍稍的有些复杂。首先我们先创建了一个ATM机的对象,然后在无卡状态下插入银行卡,紧接着在有卡状态下有插入一张银行卡(这个肯定会插入失败的)。然后在有卡状态下输入密码、输入正确的金额取款。取款成功后再次输入密码和大笔金额(肯定提示余额不足),然后在余额不足的状态下进行退卡。测试用例具体如下:

上面测试用例运行结果如下。从下方的输出结果中我们可以看出,无卡状态下插入卡会从无卡状态变为有卡状态。然后在有卡状态下再次插入银行卡会提示“目前已有银行卡,可以输入密码进行取款”。有卡状态下我们可以进行密码输入,金额输入,进行取款。取款成功可以再次输入密码进行二次取款,取款时如果余额不足,那么会成为余额不足状态。在余额不足状态时可以进行再次取款或者退出银行卡。说这么多,都是对上面状态图的反应。

二、使用“状态模式”重构

上述代码虽然能运行,但是问题是非常多的。且不说素有的东西都堆在ATM()这个类中,如果我们添加了一种状态,那么上面所提到的五个方法都得改变,这显然是不行的,在此是五个方法,加入是10个,二十个呢,就没法搞了。所以我们要换一种思路来解决这个问题。那么就是使用“状态模式”。经过我们的分析,状态有可能会改变,所以我们要讲变化的放在一块不变的放在一块。可上面那种设计方式不好将变化的部分进行提取,因为上面代码是动作中含有各种状态。

我们换一种思路,就是将状态含有动作。也就是说一种状态下有各种操作,而不是上面的一种动作中有各种状态。这个思路如果能转变过来,那么我们的状态模式就好理解多了。如果状态下含有各种动作的话,当新增一种状态时只需要将该状态包括这些动作即可,而不影响其他的状态。而最初的实现方式新增一种状态则需要修改每个动作的内容。接下来我们就是要实现“状态包含不同的动作”,在状态执行动作时,会根据该状态下的该动作来对ATM机的当前状态进行修改,也就是引入“状态模式”。具体实现方式如下:

1.“类图”的设计

因为引入“状态模式”后,我们的ATM机稍微有些复杂,所以在此我们给出了类图的设计。下方就是我们将要使用代码进行实现的类图。要想实现“状态包含个个动作”的最好的方式就是为每个状态声明一个类,然后在类中实现该状态下的不同的操作。因为我们要依赖接口编程,而不依赖实现编程。所以我们创建了两个接口,一个是状态接口StateType,另一个则是ATM机接口ATMType。在StateType中声明了状态要包含的动作,因为这些动作也是ATM机的动作,所以我们的ATMType接口也遵循StateType接口。这也等同于ATMType中同样声明了这些动作,这些动作也是ATM机必须实现的。ATM机的类则遵循与ATMType协议。BaseStateClass是所有状态的基类,该状态的基类依赖于ATMType接口,因为在状态中执行一些动作时会修改ATM机的状态,所以BaseStateClass会依赖于ATMType协议。

所有的状态类都继承自BaseStateClass,同样给出了该状态下的一些特定动作。在一些状态下执行一些动作时会修改ATM的动作。比如在ATM无卡状态下调用插卡动作(insertBankCard())方法,那么ATM机的状态就会懂无卡状态变为有卡状态。ATM机类的动作是依赖于状态的,所以ATM机依赖于状态的接口,而不依赖于状态的具体实现。下方就是引入状态模式的类图。

2. 代码实现

有了类图在给出代码实现则简单许多,首先我们会相关的接口和基类。因为我们要面向接口编程,而不是面向实现编程,所以在程序设计之初我们要先定义接口。下方就是我们定义的StateType接口和ATMType接口,以及BaseStateClass基类。StateType接口定义了所有具体的状态必须要实现的方法。而ATMType协议继承自StateType,在StateType的基础上添加了ATM机特有的方法,ATMType中声明的方法也是ATM具体实现类中必须要实现的方法。代码实现具体如下所示:

下方具体给出了无卡状态类的实现方式,在无卡状态下如果你插入银行卡,也就是调用insertBankCard()方法,那么ATM机的状态就会改变成“有卡状态”,具体实现方式如下。在无卡状态下,调用insertBankCard()以外的方法则不会改变ATM()机的状态。其余的状态的实现方式与无卡状态类的实现方式类似,就是在合适的动作中改变ATM机对象的状态。因篇幅有限,在本篇博客中就不给出具体实现了,具体实现请参加github上分享的完整实例。

接下来就是要重构我们的ATM机类了,ATM类要遵循ATMType协议。其中的stateObject成员变量的类型就是stateType类型的对象,该对象就是ATM机当前所处的状态对象,该状态对象模式是NoBankCardState类的对象。在ATM类中的动作是调用stateObject对象中相应的方法,因为状态对象中已经封装了该状态下所有的动作,所以在ATM类中直接调用即可。如果ATM机状态被改变了,那么stateObject所执行的动作也会不同,这也就是常说的多态了。下方的changeState()方法其实可以提取出类封成一个简单工厂的,因为在此我们的主题是“状态模式”,所以在此就没有进行封装。ATM类的具体实现方式如下。

3、测试用例

重构后的测试用例参照上一部分的测试用例,因为对外的类名以及动作没有发生变化,所以之前的测试用例还是可以使用的。这也就是我们之前所说的重构的魅力,所以在此的测试用例就省略了。

至此,我们的状态模式就介绍完了。状态模式其实就是封装了基于状态的行为,并将行为委托到当前状态中。有时候换一种思路会起到不一样的效果。第一部分是动作包含各种状态,而重构时我们使用了状态包括不同动作的方式引入了“状态模式”。因为博客中的代码是部分代码,完整实例请看github上分享的代码.

上述代码gitHub分享地址为:https://github.com/lizelu/DesignPatterns-Swift

设计模式(十二):通过ATM取款机来认识“状态模式”(State Pattern)的更多相关文章

  1. 乐在其中设计模式(C#) - 状态模式(State Pattern)

    原文:乐在其中设计模式(C#) - 状态模式(State Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 状态模式(State Pattern) 作者:webabcd 介绍 允 ...

  2. C#设计模式——状态模式(State Pattern)

    一.概述在面向对象软件设计时,常常碰到某一个对象由于状态的不同而有不同的行为.如果用if else或是switch case等方法处理,对象操作及对象的状态就耦合在一起,碰到复杂的情况就会造成代码结构 ...

  3. [设计模式] 20 状态模式 State Pattern

    在GOF的<设计模式:可复用面向对象软件的基础>一书中对状态模式是这样说的:允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类.状态模式的重点在于状态转换,很多时候,对 ...

  4. 二十四种设计模式:状态模式(State Pattern)

    状态模式(State Pattern) 介绍允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它所属的类. 示例有一个Message实体类,对它的操作有Insert()和Get()方法, ...

  5. 【UE4 设计模式】状态模式 State Pattern

    概述 描述 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类. 其别名为状态对象(Objects for States),状态模式是一种对象行为型模式. 有限状态机(FSMs) ...

  6. 设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型)

     设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决,不能解决就 ...

  7. 设计模式 ( 十二 ) 职责链模式(Chain of Responsibility)(对象行为)

     设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决.不能解决就 ...

  8. 小菜学习设计模式(二)—单例(Singleton)模式

    前言 设计模式目录: 小菜学习设计模式(一)—模板方法(Template)模式 小菜学习设计模式(二)—单例(Singleton)模式 小菜学习设计模式(三)—工厂方法(Factory Method) ...

  9. 北风设计模式课程---状态模式State(对象行为型)

    北风设计模式课程---状态模式State(对象行为型) 一.总结 一句话总结: 状态模式 具体状态的行为在具体的状态类中就解决,不用交给外部做判断.实质是将多条件判断弄成了多个类,在不同的类中做判断 ...

随机推荐

  1. 网络原因导致 npm 软件包 node-sass / gulp-sass 安装失败的处理办法

    如果你正在构建一个基于 gulp 的前端自动化开发环境,那么极有可能会用到 gulp-sass ,由于网络原因你可能会安装失败,因为安装过程中部分细节会到亚马逊云服务器上获取文件.本文主要讨论在不变更 ...

  2. redis成长之路——(一)

    为什么使用redis Redis适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就 ...

  3. JDBC增加删除修改

    一.配置程序--让我们程序能找到数据库的驱动jar包 1.把.jar文件复制到项目中去,整合的时候方便. 2.在eclipse项目右击"构建路径"--"配置构建路径&qu ...

  4. PHP设计模式(六)原型模式(Prototype For PHP)

    原型设计模式: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 原型设计模式简单的来说,顾名思义, 不去创建新的对象进而保留原型的一种设计模式. 缺点:原型设计模式是的最主要的缺点就 ...

  5. 【干货分享】流程DEMO-补打卡

    流程名: 补打卡申请 业务描述: 当员工在该出勤的工作日出勤但漏打卡时,于一周内填写补打卡申请. 流程相关文件: 流程包.xml 流程说明: 直接导入流程包文件,即可使用本流程 表单:  流程: 图片 ...

  6. Lucene4.4.0 开发之排序

    排序是对于全文检索来言是一个必不可少的功能,在实际运用中,排序功能能在某些时候给我们带来很大的方便,比如在淘宝,京东等一些电商网站我们可能通过排序来快速找到价格最便宜的商品,或者通过排序来找到评论数最 ...

  7. 简述我的SOA服务治理

    SOA服务治理 1.解决业务部门服务冲突和纠纷2.版本定义与版本管理3.服务备案与服务管理4.业务监督与服务监控 SOA的战略目的 一.业务价值胜过技术策略 二.战略目标胜过具体项目的效益 三.内置的 ...

  8. DB2重启数据库实例

    DB2重启数据库实例时,有时停止实例会失败,此时需要先确认没有应用链接数据库,然后再关闭数据库实例,并重新启动. 1.查看是否有活动的链接 命令:db2 list applications for d ...

  9. 如何查看w3p.exe 和IIS 应用程序池的关系

    图形界面方式 命令行方式 如果找不到 appcmd Appcmd.exe exists at the location %systemroot%\system32\inetsrv\. You eith ...

  10. Python学习实践------正向最大匹配中文分词

    正向最大匹配分词: 1.加载词典文件到集合中,取词典文件中最大长度词的length 2.每次先在句子中按最大长度分割,然后判断分割的词是否存在字典中,存在则记录此词,调整起始点. 3.不存在则按最大长 ...