说到状态模式,如果你看过之前发布的重构系列的文章中的《代码重构(六):代码重构完整案例》这篇博客的话,那么你应该对“状态模式”并不陌生,因为我们之前使用到了状态模式进行重构。上一篇博客我们讲的主题是“组合模式”,我们使用组合模式创建了一个树形结构,并给出了遍历方式。今天我们来认识一下另一种模式,那就是“状态模式”,今天就从银行的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. wepack+sass+vue 入门教程(二)

    六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...

  2. 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户

    阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...

  3. 谈一下关于CQRS架构如何实现高性能

    CQRS架构简介 前不久,看到博客园一位园友写了一篇文章,其中的观点是,要想高性能,需要尽量:避开网络开销(IO),避开海量数据,避开资源争夺.对于这3点,我觉得很有道理.所以也想谈一下,CQRS架构 ...

  4. hibernate多对一双向关联

    关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...

  5. Android GridView 通过seletor 设置状态和默认状态

    Android中可以通过selector控制GridView Item 的状态,而省去使用代码控制 GridView View Selector Xml文件 <?xml version=&quo ...

  6. 旺财速啃H5框架之Bootstrap(三)

    好多天没有写了,继续走起 在上一篇<<旺财速啃H5框架之Bootstrap(二)>>中已经把CSS引入到页面中,接下来开始写页面. 首先有些问题要先处理了,问什么你要学boot ...

  7. 解读发布:.NET Core RC2 and .NET Core SDK Preview 1

    先看一下 .NET Core(包含 ASP.NET Core)的路线图: Beta6: 2015年7月27日 Beta7: 2015年9月2日 Beta8: 2015年10月15日 RC1: 2015 ...

  8. DataTable 转换成 Json的3种方法

    在web开发中,我们可能会有这样的需求,为了便于前台的JS的处理,我们需要将查询出的数据源格式比如:List<T>.DataTable转换为Json格式.特别在使用Extjs框架的时候,A ...

  9. CentOS下mysql数据库常用命令总结

    mysql数据库使用总结 本文主要记录一些mysql日常使用的命令,供以后查询. 1.更改root密码 mysqladmin -uroot password 'yourpassword' 2.远程登陆 ...

  10. 一年之计在于春,2015开篇:PDF.NET SOD Ver 5.1完全开源

    前言: 自从我2014年下半年到现在的某电商公司工作后,工作太忙,一直没有写过一篇博客,甚至连14年股票市场的牛市都错过了,现在马上要过年了,而今天又是立春节气,如果再不动手,那么明年这个无春的年,也 ...