【一起学系列】之状态模式:你听过“流程”模式吗?
意图
允许一个对象在其内部状态改变时改变它的行为
说人话:允许对象在改变自身状态时候,更改绑定的特定方法
状态模式的诞生
【产品】:Hello,开发小哥,我们需要开发一款 娃娃机
,你可以提前想想怎么设计它啦。
【开发】:娃娃机?我想想奥,它需要投币,用户移动,确认抓取,结束这几个动作,好像很好做欸,用一个变量维护它当前的阶段,然后写四个 if 语句就好啦。
【BOSS】:你准备用一个主方法,四个子方法配合 if 语句外加一个状态变量去做吗?
// 伪代码
public void handle() {
if (flag == A) {
a();
}
if (flag == B) {
b();
}
}
【开发】:对啊,老大,你真是我肚子里的蛔虫!
【BOSS】:蛔你个头,这样做 大错特错! ,你难道想对 投币口,按钮,摇杆都绑定同一个方法吗?
【开发】:对哦,它们应该是 不同的方法,同时暴露给用户,我再思考思考
HeadFirst 核心代码
「定义状态接口,同时封装变化,利用default关键字封装默认方法」
public interface State {
/** 投币 **/
default void giveMoney() {
System.out.println("无法投币");
}
/** 移动滑杆 **/
default void move() {
System.out.println("无法移动滑杆");
}
/** 抓取 **/
default void grab() {
System.out.println("无法抓取");
}
void changeState();
}
「投币状态 状态的其中之一」
public class MoneyState implements State{
Context context;
public MoneyState(Context context) {
this.context = context;
}
@Override
public void giveMoney() {
System.out.println("已投币!");
changeState();
}
@Override
public void changeState() {
context.setExecute(new MoveState(context));
}
}
为了尽量减少代码,只展示了其中一种状态,我们可以看到在 MoneyState 状态类执行所属的业务方法时,更改了上下文持有的状态类,这就产生了 状态的变更 ,同时上下文更加清晰,即:我只用考虑我下一个状态是什么
状态模式的设计思路:
- Context 上下文环境,持有状态
- State 状态顶层接口
- ConcreteState 具体的状态
简单来说,
- 必须清晰的认识到共有多少种不同的状态,并通过接口定义其核心方法,封装变化
- 状态类持有 Context 上下文,在核心方法处理后更改其状态
❝如果看着有点模棱两可,建议看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面 ❞
状态模式的关键
- 明确所有可能发生的状态,及其转换关系
- 明确状态模式中的各个状态是有可能同时暴露给用户的
❝就好像娃娃机运作的多种状态, 投币,移动摇杆,按下确认按钮等等可能不按先后顺序触发 ❞
整一个 “流程” 模式
每个状态的方法名都一样会如何?
上文中我们大概知道了状态模式的特点,把状态封装成类,在调用状态-核心方法时候更改其状态本身,此时考虑的多种状态方法名可能各不相同,假设我们都起一样的名字会如何?
我们会首先遇到一个问题,我们无法得知它需要调用几次方法(因为可能有重复性 A - B 的情况),但如果无限循环,在适当的地方控制其结束点,和是否继续执行的标识,好像就可以解决了。
来一个流程案例
简单描述下即:开始处理订单
- 正常则进入成功状态,入库,结束执行
- 失败则进入失败状态,检测是否重新执行,扭转状态为处理订单
上代码
「Context 上下文」
public class Context {
/**
* 最大执行次数
*/
public static final Integer FAIL_NUM = 3;
/***
* 失败次数
*/
private int failNum;
/**
* 是否继续执行的标识
*/
private boolean isAbandon;
/***
* 当前状态
*/
private StateInterface stateInterface;
public Context() {
this.stateInterface = new HandleOrder();
this.failNum = 1;
this.isAbandon = false;
}
/***
* 处理方法
*/
public void handle () {
stateInterface.doAction(this);
}
// 省略无用代码...
}
「处理订单状态」
public class HandleOrder implements StateInterface {
@Override
public void doAction(Context context) {
printCurrentState();
// do somethings
int num = (int) (Math.random() * 11);
if (num >= 8) {
System.out.println("处理订单完成, 进入成功状态...");
context.setStateInterface(new SuccessOrder());
} else {
System.out.println("处理订单失败, 进入失败状态...");
context.setStateInterface(new FailOrder());
}
CodeUtils.spilt();
}
@Override
public StateEnums getCurrentState() {
return StateEnums.HANDLE_ORDER;
}
}
「客户端调用方法」
public class App {
public static void main(String[] args) {
// 模拟从队列中取任务按流程循环执行
Context context = new Context();
while (true) {
// 校验是否为废弃 | 已完成任务
if (context.isAbandon()) {
System.out.println("此条任务不再执行... ");
break;
}
context.handle();
}
}
}
测试结果输出:
当前状态:订单处理
处理订单失败, 进入失败状态...
------------------------
当前状态:处理订单失败
订单处理失败... 当前执行次数: 1
------------------------
当前状态:订单处理
处理订单失败, 进入失败状态...
------------------------
当前状态:处理订单失败
订单处理失败... 当前执行次数: 2
------------------------
当前状态:订单处理
处理订单完成, 进入成功状态...
------------------------
当前状态:处理订单成功
订单处理完成 -> 进入入库逻辑...
入库处理完成
------------------------
此条任务不再执行...
❝如果看着有点模棱两可,建议看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面 ❞
“流程” 模式适用的场景
在这样的设计中,与其说是状态的变更,不如说是 “流程” 的变更更为贴切,因此它可以作为诸多后台任务的解决方案,尤其是面临很多业务流程场景时,可以极大的提高代码的可维护性:我只用考虑和我有关的 “流程”
遵循的设计原则
- 封装变化:在父级接口中提供 default 方法,子类实现其对应的状态方法即可
- 多用组合,少用继承:状态模式经常和策略模式做对比,它们都是利用组合而非继承增强其变化和能力
什么场景适合使用状态模式
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变其行为
- 一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态
最后
「附上GOF一书中对于状态模式的UML图:」
相关代码链接
GitHub地址:https://github.com/kkzhilu/Kerwin-DesignPattern
- 兼顾了《HeadFirst》以及《GOF》两本经典书籍中的案例
- 提供了友好的阅读指导
- 【下载】苹果发布Turi Create机器学习框架,5行代码开发图像识别
- codevs 4163 hzwer与逆序对
- ASP.NET Core提供模块化Middleware组件
- CSS预处理器的对比 — sass、less和stylus
- Gensim实现Word2Vec的Skip-Gram模型简介快速上手对语料进行分词使用gensim的word2vec训练模型
- React第三方组件4(状态管理之Reflux的使用②TodoList上)
- 机器学习(六)Sigmoid函数和Softmax函数1 Sigmoid函数2 Softmax函数
- React第三方组件4(状态管理之Reflux的使用①简单使用)
- React第三方组件3(状态管理之Flux的使用⑤异步操作)
- 使用yo-get下载视频网站视频或其
- React多页面应用3(webpack4 多页面实现)
- 洛谷P2345 奶牛集会
- React多页面应用2(webpack4 处理CSS及图片,引入postCSS,及图片处理等)
- React多页面应用1(webpack4 开发环境搭建,包括热更新,api转发等)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 堆和栈的区别
- 用ABAP模拟JavaScript的柯里化语言特性(Curry)
- 使用ABAP并发编程解决一个实际应用场景中的性能瓶颈问题
- SAP Cloud for Customer Oberon视图里的Ruby Scrip
- 干了SAP开发这么多年,我都积累了哪些程序调试技巧
- 谈谈SOCKET
- 使用ABAP实现Mock测试工具Mockito
- 增强版本的自开发SAP WebClient UI Repository Information System
- 最大子序列和的问题的解(1)
- 10-STM32+ESP8266+AIR202远程升级方案-功能3-手机APP控制STM32远程更新固件程序,基于ESP8266
- 最大子序列和的接口函数(2)
- 最大子序列和的接口函数(3)
- 【剑指Offer】二叉树的深度
- 运行时间中的对数
- IIC协议