说到状态模式,如果你看过之前发布的重构系列的文章中的《代码重构(六):代码重构完整案例》这篇博客的话,那么你应该对“状态模式”并不陌生,因为我们之前使用到了状态模式进行重构。上一篇博客我们讲的主题是“组合模式”,我们使用组合模式创建了一个树形结构,并给出了遍历方式。今天我们来认识一下另一种模式,那就是“状态模式”,今天就从银行的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. 构建一个基本的前端自动化开发环境 —— 基于 Gulp 的前端集成解决方案(四)

    通过前面几节的准备工作,对于 npm / node / gulp 应该已经有了基本的认识,本节主要介绍如何构建一个基本的前端自动化开发环境. 下面将逐步构建一个可以自动编译 sass 文件.压缩 ja ...

  2. 如何正确使用日志Log

    title: 如何正确使用日志Log date: 2015-01-08 12:54:46 categories: [Python] tags: [Python,log] --- 文章首发地址:http ...

  3. Hawk 6. 编译和扩展开发

    Hawk是开源项目,因此任何人都可以为其贡献代码.作者也非常欢迎使用者能够扩展出更有用的插件. 编译 编译需要Visual Stuido,版本建议使用2015, 2010及以上没有经过测试,但应该可以 ...

  4. Oracle碎碎念~2

    1. 如何查看表的列名及类型 SQL> select column_name,data_type,data_length from all_tab_columns where owner='SC ...

  5. PHP之用户验证和标签推荐的简单使用

    本篇主要是讲解一些最简单的验证知识 效果图 bookmark_fns.php <?php require_once('output_fns.php'); require_once('db_fns ...

  6. 【NLP】蓦然回首:谈谈学习模型的评估系列文章(一)

    统计角度窥视模型概念 作者:白宁超 2016年7月18日17:18:43 摘要:写本文的初衷源于基于HMM模型序列标注的一个实验,实验完成之后,迫切想知道采用的序列标注模型的好坏,有哪些指标可以度量. ...

  7. javascript之闭包理解以及应用场景

    半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...

  8. 浅谈java异常[Exception]

    学习Java的同学注意了!!! 学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:589809992 我们一起学Java! 一. 异常的定义 在<java编程思想 ...

  9. css样式之超出隐藏

    文本超出部分隐藏,总结两种方法. 1.单行隐藏 html代码 <div class="mi">当文字超过范围的时候,超出部分会隐藏起来.</div> css ...

  10. git和pycharm管理代码

    首先明白三个概念,服务器代码库,本地代码库,和正在coding的项目. coding完毕后,先通过commit提交到本地代码库,然后通过push再提交server的代码库    git步骤 git c ...