设计模式(十二):通过ATM取款机来认识“状态模式”(State Pattern)
说到状态模式,如果你看过之前发布的重构系列的文章中的《代码重构(六):代码重构完整案例》这篇博客的话,那么你应该对“状态模式”并不陌生,因为我们之前使用到了状态模式进行重构。上一篇博客我们讲的主题是“组合模式”,我们使用组合模式创建了一个树形结构,并给出了遍历方式。今天我们来认识一下另一种模式,那就是“状态模式”,今天就从银行的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)的更多相关文章
- 乐在其中设计模式(C#) - 状态模式(State Pattern)
原文:乐在其中设计模式(C#) - 状态模式(State Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 状态模式(State Pattern) 作者:webabcd 介绍 允 ...
- C#设计模式——状态模式(State Pattern)
一.概述在面向对象软件设计时,常常碰到某一个对象由于状态的不同而有不同的行为.如果用if else或是switch case等方法处理,对象操作及对象的状态就耦合在一起,碰到复杂的情况就会造成代码结构 ...
- [设计模式] 20 状态模式 State Pattern
在GOF的<设计模式:可复用面向对象软件的基础>一书中对状态模式是这样说的:允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类.状态模式的重点在于状态转换,很多时候,对 ...
- 二十四种设计模式:状态模式(State Pattern)
状态模式(State Pattern) 介绍允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它所属的类. 示例有一个Message实体类,对它的操作有Insert()和Get()方法, ...
- 【UE4 设计模式】状态模式 State Pattern
概述 描述 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类. 其别名为状态对象(Objects for States),状态模式是一种对象行为型模式. 有限状态机(FSMs) ...
- 设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型)
设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决,不能解决就 ...
- 设计模式 ( 十二 ) 职责链模式(Chain of Responsibility)(对象行为)
设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决.不能解决就 ...
- 小菜学习设计模式(二)—单例(Singleton)模式
前言 设计模式目录: 小菜学习设计模式(一)—模板方法(Template)模式 小菜学习设计模式(二)—单例(Singleton)模式 小菜学习设计模式(三)—工厂方法(Factory Method) ...
- 北风设计模式课程---状态模式State(对象行为型)
北风设计模式课程---状态模式State(对象行为型) 一.总结 一句话总结: 状态模式 具体状态的行为在具体的状态类中就解决,不用交给外部做判断.实质是将多条件判断弄成了多个类,在不同的类中做判断 ...
随机推荐
- 构建一个基本的前端自动化开发环境 —— 基于 Gulp 的前端集成解决方案(四)
通过前面几节的准备工作,对于 npm / node / gulp 应该已经有了基本的认识,本节主要介绍如何构建一个基本的前端自动化开发环境. 下面将逐步构建一个可以自动编译 sass 文件.压缩 ja ...
- 如何正确使用日志Log
title: 如何正确使用日志Log date: 2015-01-08 12:54:46 categories: [Python] tags: [Python,log] --- 文章首发地址:http ...
- Hawk 6. 编译和扩展开发
Hawk是开源项目,因此任何人都可以为其贡献代码.作者也非常欢迎使用者能够扩展出更有用的插件. 编译 编译需要Visual Stuido,版本建议使用2015, 2010及以上没有经过测试,但应该可以 ...
- Oracle碎碎念~2
1. 如何查看表的列名及类型 SQL> select column_name,data_type,data_length from all_tab_columns where owner='SC ...
- PHP之用户验证和标签推荐的简单使用
本篇主要是讲解一些最简单的验证知识 效果图 bookmark_fns.php <?php require_once('output_fns.php'); require_once('db_fns ...
- 【NLP】蓦然回首:谈谈学习模型的评估系列文章(一)
统计角度窥视模型概念 作者:白宁超 2016年7月18日17:18:43 摘要:写本文的初衷源于基于HMM模型序列标注的一个实验,实验完成之后,迫切想知道采用的序列标注模型的好坏,有哪些指标可以度量. ...
- javascript之闭包理解以及应用场景
半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...
- 浅谈java异常[Exception]
学习Java的同学注意了!!! 学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:589809992 我们一起学Java! 一. 异常的定义 在<java编程思想 ...
- css样式之超出隐藏
文本超出部分隐藏,总结两种方法. 1.单行隐藏 html代码 <div class="mi">当文字超过范围的时候,超出部分会隐藏起来.</div> css ...
- git和pycharm管理代码
首先明白三个概念,服务器代码库,本地代码库,和正在coding的项目. coding完毕后,先通过commit提交到本地代码库,然后通过push再提交server的代码库 git步骤 git c ...