为了减少代码复杂度,我将if-else升级为面向状态编程
摘要:面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判断。
本文分享自华为云社区《从面向if-else编程升级为面向状态编程,减少代码复杂度》,作者:breakDraw。
面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判断。如果你还在大量使用if else,当然,界面表现层除外,即使你使用Java/C#这样完全面向对象的语言,也只能说明你的思维停留在传统的面向过程语言上。
需求
有一个非常经典的数字校验场景, 需求如下:

复杂度高的硬写代码
这时候如果直接硬写,大概率写出容易复杂度巨高的代码,还容易遗漏而出错。
例子如下:
class Solution {
public boolean isNumber(String s) {
int sign = 1;
int pointSign = 1;
int eSign = 1;
int numSign = -1;
int i = 0;
int n = s.length();
while(i<n){
if(s.charAt(i)>='0'&&s.charAt(i)<='9'){
numSign = 1;
sign = -1;
}else if(s.charAt(i)=='+'||s.charAt(i)=='-'){
if(sign>0){
sign = -sign;
}else{
return false;
}
if(i>0&&s.charAt(i-1)=='.'){
return false;
}
}else if(s.charAt(i)=='.'){
//numSign = -1;
if(pointSign>0){
pointSign = -pointSign;
}else{
return false;
}
if(i>0&&(s.charAt(i-1)=='e'||s.charAt(i-1)=='E')){
return false;
}
}else if(s.charAt(i)=='e'||s.charAt(i)=='E'){
if(eSign<0||numSign<0){
return false;
}
eSign = -1;
sign = 1;
numSign = -1;
pointSign = -1;
}else{
return false;
}
i++;
}
return numSign>0;
}
}
这段代码的复杂度为 21, 放在科目一考试直接不及格了,而且非常容易出错,改着改着把自己改晕了,或者改漏了。

§ 状态机优化
图片引用自Leetcode官方题解,链接见:
https://leetcode-cn.com/problems/valid-number/solution/you-xiao-shu-zi-by-leetcode-solution-298l/

可以看到校验的过程可以组成一个状态, 当遇到特定字符时,进入特定的状态去判断,并且该状态后面只能接入有限的状态。因此我们可以定义N个状态,每个状态定义X个状态变化条件和变化状态。
在java中用多个map即可进行维护这种关系。
可以写出如下的代码, 虽然代码量看起来更高了,但是可维护性和复杂度变强不少。
class Solution {
public enum CharType {
NUMBER,
OP,
POINT,
E;
public static CharType toCharType(Character c) {
if (Character.isDigit(c)) {
return NUMBER;
} else if (c == '+' || c == '-') {
return OP;
} else if (c == '.') {
return POINT;
} else if (c =='e' || c == 'E') {
return E;
} else {
return null;
}
}
}
public enum State {
INIT(false),
OP1(false),
// 在.前面的数字
BEFORE_POINT_NUMBER(true),
// 前面没数字的点
NO_BEFORE_NUMBER_POINT(false),
// 前面有数字的点
BEFORE_NUMBER_POINT(true),
// 点后面的数字
AFTER_POINT_NUMBER(true),
// e/E
OPE(false),
// E后面的符号
OP2(false),
// e后面的数字
AFTER_E_NUMBER(true);
// 是否可在这个状态结束
private boolean canEnd;
State(boolean canEnd) {
this.canEnd = canEnd;
}
public boolean isCanEnd() {
return canEnd;
}
}
public Map<State, Map<CharType, State>> transferMap = new HashMap<>() {{
Map<CharType, State> map = new HashMap<>() {{
put(CharType.OP, State.OP1);
put(CharType.NUMBER, State.BEFORE_POINT_NUMBER);
put(CharType.POINT, State.NO_BEFORE_NUMBER_POINT);
}};
put(State.INIT, map);
map = new HashMap<>() {{
put(CharType.POINT, State.NO_BEFORE_NUMBER_POINT);
put(CharType.NUMBER, State.BEFORE_POINT_NUMBER);
}};
put(State.OP1, map);
map = new HashMap<>() {{
put(CharType.POINT, State.BEFORE_NUMBER_POINT);
put(CharType.NUMBER, State.BEFORE_POINT_NUMBER);
put(CharType.E, State.OPE);
}};
put(State.BEFORE_POINT_NUMBER, map);
map = new HashMap<>() {{
put(CharType.NUMBER, State.AFTER_POINT_NUMBER);
}};
put(State.NO_BEFORE_NUMBER_POINT, map);
map = new HashMap<>() {{
put(CharType.NUMBER, State.AFTER_POINT_NUMBER);
put(CharType.E, State.OPE);
}};
put(State.BEFORE_NUMBER_POINT, map);
map = new HashMap<>() {{
put(CharType.E, State.OPE);
put(CharType.NUMBER, State.AFTER_POINT_NUMBER);
}};
put(State.AFTER_POINT_NUMBER, map);
map = new HashMap<>() {{
put(CharType.OP, State.OP2);
put(CharType.NUMBER, State.AFTER_E_NUMBER);
}};
put(State.OPE, map);
map = new HashMap<>() {{
put(CharType.NUMBER, State.AFTER_E_NUMBER);
}};
put(State.OP2, map);
map = new HashMap<>() {{
put(CharType.NUMBER, State.AFTER_E_NUMBER);
}};
put(State.AFTER_E_NUMBER, map);
}};
public boolean isNumber(String s) {
State state = State.INIT;
for (char c : s.toCharArray()) {
Map<CharType, State> transMap = transferMap.get(state);
CharType charType = CharType.toCharType(c);
if (charType == null) {
return false;
}
if (!transMap.containsKey(charType)) {
return false;
}
// 状态变更
state = transMap.get(charType);
}
return state.canEnd;
}
}
可以看到复杂度也只有8,不会复杂度超标。

为了减少代码复杂度,我将if-else升级为面向状态编程的更多相关文章
- 提升代码幸福度,五个技巧减少js开发中的if else语句
壹 ❀ 引 在JavaScript开发中,条件判断语句的使用频率是极高的,而对于条件判断简单易读的if else应该都是大家的首选.可是代码写的久了,我们总是希望自己的代码看着能更为简洁规范(逼格更 ...
- Scala减少代码重复
高阶函数可以把其它函数当作函数参数,帮助我们减少代码重复,例如: object FileMatcher { private def fileHere = (new File(".\\file ...
- 前端程序员的蜕变——JS的 event 对象属性、使用实例、兼容性处理(极大提高代码效率、减少代码量)
下面讨论一下 js 中的 Event 对象,主要从以下三个方面详细的描述(点击标题可跳转到对应部分): 1.什么是event 2.怎么用event,用他该注意什么,几个简单实际应用 3.event在不 ...
- OpenCV:Mat元素访问方法、性能、代码复杂度以及安全性分析
欢迎转载,尊重原创,所以转载请注明出处: http://blog.csdn.net/bendanban/article/details/30527785 本文讲述了OpenCV中几种访问矩阵元素的方法 ...
- 使用python对py文件程序代码复用度检查
#!/user/bin/env python # @Time :2018/6/5 14:58 # @Author :PGIDYSQ #@File :PyCheck.py from os.path im ...
- VS 提升代码辨识度 (工欲善其事必先利其器)新手开发必备!
VS简化编译.提高生产 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享.心创 ...
- VS、ReSharper 设置修改代码颜色、提高代码辨识度!附VS超实用快捷!
ReSharper 配置代码颜色 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心 ...
- 【codenet】代码相似度计算框架调研 -- 把内容与形式分开
首发于我的gitpages博客 https://helenawang.github.io/2018/10/10/代码相似度计算框架调研 代码相似度计算框架调研 研究现状 代码相似度计算是一个已有40年 ...
- 相比xib 使用代码编排view 的一个明显的好处就是可以更好地重复使用已有代码,减少代码冗余。
相比xib 使用代码编排view 的一个明显的好处就是可以更好地重复使用已有代码,减少代码冗余.
- WPF INotifyPropertyChanged 通过特性减少代码量
在很多地方需要用上INotifyPropertyChanged的接口,MVVM模式,List等集合都会用到. 通常我们使用 protected void OnChange(PropertyChange ...
随机推荐
- 如何使用SHC对Shell脚本进行封装和源码隐藏
在许多情况下,我们需要保护我们的shell脚本源码不被别人轻易查看.这时,使用shc工具将shell脚本编译成二进制文件是一个有效的方法.本文将详细介绍如何在线和离线条件下安装shc,并将其用于编译你 ...
- codeforces #864 div2 B
GCD Partition 这道题首先要解决一个问题,要把区间分成几块,可以证明分成两块是更优 首先我们假设把区间分成了m(>= 2)块 b1, b2, b3, ...,bm,则答案是gcd(b ...
- 如何优雅使用 vuex
大纲 本文内容更多的是讲讲使用 vuex 的一些心得想法,所以大概会讲述下面这些点: Q1:我为什么会想使用 vuex 来管理数据状态交互? Q2:使用 vuex 框架有哪些缺点或者说副作用? Q3: ...
- 二、RHEL8操作系统安装
一.如何安装rhel的操作系统? 必要的前提条件:硬件(CPU.内存.硬盘--) + 安装介质(操作系统的安装文件) 会不会把自己的笔记本装成rhel的操作系统呢? 不会 使用虚拟机软 ...
- 旋转矩阵(leetcode4.7每日打卡)
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节.请你设计一种算法,将图像旋转 90 度. 不占用额外内存空间能否做到? 示例 1: 给定 matrix = [ [1,2, ...
- freeswitch的一个性能问题
概述 freeswitch是一款简单好用的VOIP开源软交换平台. 在fs的使用过程中,会遇到各种各样的问题,各种问题中,性能问题是最头疼的. 最近在测试某些场景的时候,压测会造成fs的内存占用持续升 ...
- Welcome to YARP - 8.分布式跟踪
Welcome to YARP - 1.认识YARP并搭建反向代理服务 Welcome to YARP - 2.配置功能 2.1 - 配置文件(Configuration Files) 2.2 - 配 ...
- 【ASP.NET Core】MVC过滤器:运行流程
MVC 的过滤器(Filters)也翻译为"筛选器".但是老周更喜欢翻译为"过滤器",意思上更好理解. 既然都叫过滤器了,就是在MVC的操作方法调用前后进行特殊 ...
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-35-处理web页面定位toast-上篇
1.简介 在使用appium写app自动化的时候介绍toast的相关元素的定位,在Web UI测试过程中,也经常遇到一些toast(出现之后一闪而过,不留下一点点痕迹),那么这个toast我们这边如何 ...
- 深度解析C#中LinkedList<T>的存储结构
本文承接前面的3篇有关C#的数据结构分析的文章,对于C#有关数据结构分析还有一篇就要暂时结束了,这个系列主要从Array.List.Dictionary.LinkedList. SortedSet等5 ...