干掉if-else,试试状态模式!
点击关注公众号,回复“2T”获取2TB学习资源!
互联网架构师后台回复 2T 有特别礼包
来源:zhenbianshu.github.io
上一篇:不卷了!技术团队成员集体辞职
背景
玩转 Java 动态编译,实现了 Java 代码的动态编译后,接下来就要将原来使用注释配置的 Java 数据类型改为使用缩写替代。
为了便于缩写,能直观地看出完整类型,我设计的方案是:
对简单类型如 String、int、Double,就使用类型的首字母替代,如 i -> int / D -> Double;
对于容器类型如 List、Map,使用两个首字母分别标志容器的开始和闭合,如 LDL -> List<Double> / MDDM -> Map<Double,Double>;
由于 Set 的首字母和 String 首字母冲突,将 String 的缩写修改为 T,同时处理了 Long 和 List 的冲突;
支持容器类型的嵌套,如 LLTLL -> List<List<String>> / MTLDLM -> Map<String,List<Double>>;
我使用普通的 if-else 方式和状态机方式各实现了一遍,更深切地理解了状态机在处理这种多状态的复杂问题时的优越性。
两种实现的代码我都放在了 github 上,地址是:Github-zhenbianshu-java_shorten_type_parser,有类似需求的可以改改来用。
IF-ELSE 方式
原来以为写一个简单的类型翻译器花不了太多时间,可是真做起来,才发现要注意的点太多了。
首先是处理容器的开启和闭合,这就需要使用栈来保存预期的下一个字符类型,再对比栈顶字符类型和当前处理字符,决定解析的结果。
还要注意类型嵌套的情况下,内层嵌套的容器作为外层容器的元素被解析完成时,需要修改外层容器的预期字符。而且 Map 作为一种相对 Set 和 List 比较特殊的容器,还要处理它的左右元素。
同时还不能忘记处理各种异常,如未知字符、容器内是原始类型、容器未正确闭合等。
而这些逻辑混杂在一块就更添复杂度了,通常是一遍代码写下来挺顺畅,找几个特殊的 case 一验证,往往就有没有考虑到的点,你以为解决了这个点就好了,殊不知这个问题点的解决方案又引起了另一个问题。
最终修修补补好多次,终于把代码写完了,连优化的想法都没了,担心又引入新的问题。
最终的伪代码如下:
public String parseToFullType() throws IllegalStateException {StringBuilder sb = new StringBuilder();for (; ; this.scanner.next()) {Character currentChar = scanner.current();if (currentChar == '\uFFFF') {return sb.toString();}if (isCollection()) {if (CollectionEnd()) {dealCollectionEleEnd();}else {throw new IllegalStateException("unexpected char '" + currentChar + "' at position " + scanner.getIndex());}} else if (isWrapperType()) {dealSingleEleEnd();} else if (parseStart()) {if (collectionStart()) {putCollecitonExpectEle()}} else {throw new IllegalStateException("unknown char '" + currentChar + "' at position " + scanner.getIndex());}}
状态机方式
是不是看起来非常乱,这还没有列出各个方法里的条件判断语句呢。这么多逻辑混杂,造成的问题就是很难改动,因为你不知道改动会影响哪些其他逻辑。
面对这种问题,当然有一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,就是状态机。
状态机
有限状态机(finite-state machine,缩写:FSM)又称有限状态自动机(finite-state automation,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。
像我们生活中在公路上驾驶汽车就像在维护一个状态机,遇到红灯就停车喝口水,红灯过后再继续行车,遇到了黄灯就要减速慢行。而实现状态机前要首先明确四个主体:
状态 State:状态是一个系统在其生命周期的某一刻时的运行状态,如驾车的例子中状态就包括 正常速度行驶、停车和低速行驶三种状态。
事件 Event:事件就是某一时刻施加于系统的某个信号,在上面的例子中事件是指红灯、绿灯和黄灯。所有的状态变化都要依赖事件,但事件也可能导致状态不发生变化,如正常行驶中遇到绿灯就不用做什么反应。
变换 Transition:变换是在事件发生之后系统要做出的状态变化,如上面例子中的减速、停车或加速。
动作 Action:动作是同样是事件发生之后系统做出的反应,不同的是,动作不会改变系统状态,像驾车遇到红灯停车后,喝水这个动作没有对系统状态造成影响。
将状态机的四种要素提取之后,就可以很简单地将状态和事件进行解耦了。
状态拆分
还是拿我的这个需求来分析,先画出状态变化图从整体上把握状态间的关系。
通过上面的图一步步拆解状态机:
首先是确定状态,我定义了 Start/SetStart/SetEle/ListStart/ListEel/MapStart/MapLeft/MapRight 八种基础状态,由于一次只解析一个类型,容器闭合就代表着解析结束,所以没有对各个容器设置结束状态。又因为有状态嵌套的存在,而一个状态没法表达状态机的准确状态,需要使用栈来存储整体的解析状态,我使用这个栈为空来代表 End 状态,又省略了一个状态。另外,搜索公众号互联网架构师回复关键字"2T”获取一份惊喜礼包。
再拆分事件,事件是扫描到的每一个字符,由于字符种类较多,而像 integer 和 double、String 和 Long 的处理又没有什么区别,我将事件类型抽象为 包装类型元素(WRAPPED_ELE),原始类型元素(PRIMITIVE_ELE),MAP、List 和 Set 五种。
变幻和动作都是事件发生后系统的反应,在我的需要里需要转变解析状态,并将结构结果保存起来。这里我将它们整体抽象为一个事件处理器接口,如:
public interface StateHandler {/*** @param event 要处理的事件* @param states 系统整体状态* @param result 解析的结果*/void handle(Event event, Stack<State> states, StringBuilder result);}
代码示例
将状态机的各个要素都抽出来之后,再分别完善每个 StateHandler 的处理逻辑就行,这部分就非常简单了,下面是 MapLeftHandler 的详情。
public class MapLeftHandler implements StateHandler {@Overridepublic void handle(Event event, Stack<State> states, StringBuilder result) {// 这里是核心的 Action,将单步解析结果放到最终结果内result.append(",");result.append(event.getParsedVal());// 状态机的典型处理方式,处理各种事件发生在当前状态时的逻辑switch (event.getEventType()) {case MAP:states.push(State.MAP_START);break;case SET:states.push(State.SET_START);break;case LIST:states.push(State.LIST_START);break;case WRAPPED_ELE:// 使用 pop 或 push 修改栈顶状态来修改解析器的整体状态states.pop();states.push(State.MAP_RIGHT);break;case PRIMITIVE_ELE:// 当前状态不能接受的事件类型要抛异常中断throw new IllegalStateException("unexpected primitive char '" + event.getCharacter() + "' at position " + event.getIndex());default:}}
}
主类内的代码如下:
public static String parseToFullType(String shortenType) throws IllegalStateException {StringBuilder result = new StringBuilder();StringCharacterIterator scanner = new StringCharacterIterator(shortenType);Stack<State> states = new Stack<>();states.push(State.START);for (; ; scanner.next()) {char currentChar = scanner.current();if (currentChar == '\uFFFF') {return result.toString();}// 使用整体状态为空来代表解析结束if (states.isEmpty()) {throw new IllegalStateException("unexpected char '" + currentChar + "' at position " + scanner.getIndex());}// 将字符规整成事件对象,有利于参数的传递Event event = Event.parseToEvent(currentChar, scanner.getIndex());if (event == null) {throw new IllegalStateException("unknown char '" + currentChar + "' at position " + scanner.getIndex());}// 这里需要一个 Map 来映射状态和状态处理器STATE_TO_HANDLER_MAPPING.get(states.peek()).handle(event, states, result);}}
小结
状态模式
如果你对设计模式较熟的话,会发现这不就是状态模式嘛。
有解释说,状态模式会将事件类型也再解耦,即 StateHandler 里不只有一个方法,而是会有八个方法,分别为 handleStart,HandleListEle 等,但我觉得模式并不是定式,稍微的变形是没有问题的,如果单个事件类型的处理足够复杂,将其再拆分更合理一些。
代码结构
最后,对比 if-else 实现,从代码量上来看,状态机实现增加了很多,这是解耦的代价,当然也有很多重复代码的缘故,比如在容器闭合时校验当前容器是否内嵌容器,并针对内嵌容器做处理的逻辑就完全一样,为了代码清晰我就没有再抽取方法。
从可维护性上来说,状态机实现由于逻辑拆分比较清晰,在添加或删除一种状态时比较方便,添加一个状态和状态处理器就行,但在添加一种事件类型时较为复杂,需要修改所有状态处理器里的实现,不过从整体上来看是利大于弊的,毕竟代码清晰易改动最重要。
了解了状态机实现的固定套路之后,你也可以写出高大上的状态机代码了,快 Get 起来替换掉项目里杂乱的 if-else 吧。
最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。
正文结束
推荐阅读 ↓↓↓
1.心态崩了!税前2万4,到手1万4,年终奖扣税方式1月1日起施行~
2.深圳一普通中学老师工资单曝光,秒杀程序员,网友:敢问是哪个学校毕业的?
3.从零开始搭建创业公司后台技术栈
4.程序员一般可以从什么平台接私活?
5.清华大学:2021 元宇宙研究报告!
6.为什么国内 996 干不过国外的 955呢?
7.这封“领导痛批95后下属”的邮件,句句扎心!
8.15张图看懂瞎忙和高效的区别!
干掉if-else,试试状态模式!相关推荐
- 干掉项目中杂乱的 if-else,试试状态模式,这才是优雅的实现方式!
来源:https://zhenbianshu.github.io IF-ELSE 方式 原来以为写一个简单的类型翻译器花不了太多时间,可是真做起来,才发现要注意的点太多了. 首先是处理容器的开启和 ...
- 重返设计模式--状态模式
理论要点 什么是状态模式:允许对象在当内部状态改变时改变其行为,就好像此对象改变了自己的类一样.通俗点总结就是:一个对象存在很多状态,这些状态可以通过外部输入而转移到另一个状态.即状态,输入,转移就是 ...
- JS设计模式(13)状态模式
什么是状态模式? 定义:将事物内部的每个状态分别封装成类,内部状态改变会产生不同行为. 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为. 何时使用:代码中包含大 ...
- 设计模式之状态模式(State)摘录
23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...
- 设计模式:状态模式(State Pattern)
作者:Wang Juqiang 创建于:2012-07-16 出处:http://www.cnblogs.com/wangjq/archive/2012/07/16/2593485.html 收录于 ...
- else 策略模式去掉if_设计模式(三)——简单的状态模式代替if-else
博主将会针对Java面试题写一组文章,包括J2ee,SQL,主流Web框架,中间件等面试过程中面试官经常问的问题,欢迎大家关注.一起学习,一起成长. 前言 大多数开发人员现在还在使用if else的过 ...
- 【设计模式】 模式PK:策略模式VS状态模式
1.概述 行为类设计模式中,状态模式和策略模式是亲兄弟,两者非常相似,我们先看看两者的通用类图,把两者放在一起比较一下. 策略模式(左)和状态模式(右)的通用类图. 两个类图非常相似,都是通过Cont ...
- Python设计模式-状态模式
Python设计模式-状态模式 代码基于3.5.2,代码如下; #coding:utf-8 #状态模式class state():def writeProgram(self,work):raise N ...
- 18State(状态)模式
技术交流QQ群:1027579432,欢迎你的加入! 1.状态变化模式 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定?状态变化模式为这一问题提供 ...
- Java设计模式之策略模式与状态模式
一.策略模式定义 定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们之间可以相互替换,策略模式可以在不影响客户端的情况下发生变化. 好了,定义看看就完了,我知道你很烦看定义. 二.策 ...
最新文章
- Python实现换位加密
- HFSS15.0安装步骤
- windows 指定的网络名不可用__被我解决了!
- HTML期末作业-牛排美食餐厅网站
- 1381. 设计一个支持增量操作的栈
- Python nose test framework 介绍
- 数字图像处理实验六--图像复原
- mac电脑闪屏怎么办?解决mac屏幕一闪一闪的方法
- 跑跑卡丁车rush服务器维护,跑跑卡丁车RUSH
- 模型微调------学习笔记
- 好看的皮囊 · 也是大自然的杰作 · 全球高质量 · 美图 · 集中营 · 美女 · 2017-08-29期...
- 男生必须给女友纠正的小习惯
- JavaScript的原型和原型链分析
- Python 学到什么程度才可以去找工作?掌握这 4 点足够了!
- 彻底解决不要脸的360更改浏览器主页【转载】
- 炫龙P6笔记本搭建Windows 10+Ubuntu双系统
- Qt程序打包成安装包exe
- matlab 神经网络 ANN 分类
- 2013百度校园招聘笔试
- 深圳市信息服务业区块链协会为理事单位“陀螺财经”授牌
热门文章
- 504 Gateway Time-out 错误处理记录
- WWDC21 定档,苹果眼镜成最大猜想
- 苹果收购倒闭智能家居安防初创公司 Lighthouse AI 专利...
- 轻松应对多层JSON数据计算与入库
- 用wxDraw.js制作酷炫的小程序canvas动画『wxDraw 小程序界的zrender』
- WordPress: 使用 wp_insert_attachment 上传附件
- 实施ERP系统的一般方法和步骤
- xshell连接redhat注册显示中文乱码
- 「leetcode」700. 二叉搜索树中的搜索:【递归法】【迭代法】详解
- indesign教程,如何转换图形和框架?