如何解决代码中if…else-过多的问题,建议收藏

逻辑表达模式固定的 if…else

实现与示例

if (param.equals(value1)) {
doAction1(someParams);
} else if (param.equals(value2)) {
doAction2(someParams);
} else if (param.equals(value3)) {
doAction3(someParams);
}
// …

可重构为

Map<?, Function<?> action> actionMappings = new HashMap<>(); // 这里泛型 ? 是为方便演示,实际可替换为你需要的类型

// When init
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});

// 省略 null 判断
actionMappings.get(param).apply(someParams);

上面的示例使用了 Java 8 的 Lambda 和 Functional Interface,这里不做讲解。表的映射关系,可以采用集中的方式,也可以采用分散的方式,即每个处理类自行注册。也可以通过配置文件的方式表达。总之,形式有很多。

还有一些问题,其中的条件表达式并不像上例中的那样简单,但稍加变换,同样可以应用表驱动。下面借用《编程珠玑》中的一个税金计算的例子:

if income <= 2200
tax = 0
else if income <= 2700
tax = 0.14 * (income - 2200)
else if income <= 3200
tax = 70 + 0.15 * (income - 2700)
else if income <= 3700
tax = 145 + 0.16 * (income - 3200)

else
tax = 53090 + 0.7 * (income - 102200)

对于上面的代码,其实只需将税金的计算公式提取出来,将每一档的标准提取到一个表格,在加上一个循环即可。具体重构之后的代码不给出,大家自己思考。

方法二:职责链模式

介绍

当 if…else 中的条件表达式灵活多变,无法将条件中的数据抽象为表格并用统一的方式进行判断时,这时应将对条件的判断权交给每个功能组件。并用链的形式将这些组件串联起来,形成完整的功能。

适用场景

条件表达式灵活多变,没有统一的形式。

实现与示例

职责链的模式在开源框架的 Filter、Interceptor 功能的实现中可以见到很多。下面看一下通用的使用模式:

重构前:

public void handle(request) {
if (handlerA.canHandle(request)) {
handlerA.handleRequest(request);
} else if (handlerB.canHandle(request)) {
handlerB.handleRequest(request);
} else if (handlerC.canHandle(request)) {
handlerC.handleRequest(request);
}
}

重构后:

public void handle(request) {
handlerA.handleRequest(request);
}

public abstract class Handler {
protected Handler next;
public abstract void handleRequest(Request request);
public void setNext(Handler next) { this.next = next; }
}

public class HandlerA extends Handler {
public void handleRequest(Request request) {
if (canHandle(request)) doHandle(request);
else if (next != null) next.handleRequest(request);
}
}

当然,示例中的重构前的代码为了表达清楚,做了一些类和方法的抽取重构。现实中,更多的是平铺式的代码实现。

注:职责链的控制模式

职责链模式在具体实现过程中,会有一些不同的形式。从链的调用控制角度看,可分为外部控制和内部控制两种。

外部控制不灵活,但是减少了实现难度。职责链上某一环上的具体实现不用考虑对下一环的调用,因为外部统一控制了。但是一般的外部控制也不能实现嵌套调用。

如果有嵌套调用,并且希望由外部控制职责链的调用,实现起来会稍微复杂。具体可以参考 Spring Web Interceptor 机制的实现方法。

内部控制就比较灵活,可以由具体的实现来决定是否需要调用链上的下一环。但如果调用控制模式是固定的,那这样的实现对于使用者来说是不便的。

设计模式在具体使用中会有很多变种,大家需要灵活掌握

方法三:注解驱动

介绍

通过 Java 注解(或其它语言的类似机制)定义执行某个方法的条件。在程序执行时,通过对比入参与注解中定义的条件是否匹配,再决定是否调用此方法。具体实现时,可以采用表驱动或职责链的方式实现。

适用场景

适合条件分支很多多,对程序扩展性和易用性均有较高要求的场景。通常是某个系统中经常遇到新需求的核心功能。

实现与示例

很多框架中都能看到这种模式的使用,比如常见的 Spring MVC。因为这些框架很常用,demo 随处可见,所以这里不再上具体的演示代码了。

这个模式的重点在于实现。现有的框架都是用于实现某一特定领域的功能,例如 MVC。故业务系统如采用此模式需自行实现相关核心功能。主要会涉及反射、职责链等技术。具体的实现这里就不做演示了。

方法四:事件驱动

介绍

通过关联不同的事件类型和对应的处理机制,来实现复杂的逻辑,同时达到解耦的目的。

适用场景

从理论角度讲,事件驱动可以看做是表驱动的一种,但从实践角度讲,事件驱动和前面提到的表驱动有多处不同。具体来说:

  1. 表驱动通常是一对一的关系;事件驱动通常是一对多;
  2. 表驱动中,触发和执行通常是强依赖;事件驱动中,触发和执行是弱依赖

正是上述两者不同,导致了两者适用场景的不同。具体来说,事件驱动可用于如订单支付完成触发库存、物流、积分等功能。

实现与示例

实现方式上,单机的实践驱动可以使用 Guava、Spring 等框架实现。分布式的则一般通过各种消息队列方式实现。但是因为这里主要讨论的是消除 if…else,所以主要是面向单机问题域。因为涉及具体技术,所以此模式代码不做演示。

方法五:有限状态机

介绍

有限状态机通常被称为状态机(无限状态机这个概念可以忽略)。先引用维基百科上的定义:

有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

其实,状态机也可以看做是表驱动的一种,其实就是当前状态和事件两者组合与处理函数的一种对应关系。当然,处理成功之后还会有一个状态转移处理。

适用场景

虽然现在互联网后端服务都在强调无状态,但这并不意味着不能使用状态机这种设计。其实,在很多场景中,如协议栈、订单处理等功能中,状态机有这其天然的优势。因为这些场景中天然存在着状态和状态的流转。

实现与示例

实现状态机设计首先需要有相应的框架,这个框架需要实现至少一种状态机定义功能,以及对于的调用路由功能。状态机定义可以使用 DSL 或者注解的方式。原理不复杂,掌握了注解、反射等功能的同学应该可以很容易实现。

参考技术:

  • Apache Mina State Machine
    Apache Mina 框架,虽然在 IO 框架领域不及 Netty,但它却提供了一个状态机的功能。https://mina.apache.org/mina-project/userguide/ch14-state-machine/ch14-state-machine.html。有自己实现状态机功能的同学可以参考其源码。
  • Spring State Machine
    Spring 子项目众多,其中有个不显山不露水的状态机框架 —— Spring State Machine https://projects.spring.io/spring-statemachine/。可以通过 DSL 和注解两种方式定义。

上述框架只是起到一个参考的作用,如果涉及到具体项目,需要根据业务特点自行实现状态机的核心功能。

方法六:Optional

介绍

Java 代码中的一部分 if…else 是由非空检查导致的。因此,降低这部分带来的 if…else 也就能降低整体的 if…else 的个数。

Java 从 8 开始引入了 Optional 类,用于表示可能为空的对象。这个类提供了很多方法,用于相关的操作,可以用于消除 if…else。开源框架 Guava 和 Scala 语言也提供了类似的功能。

使用场景

有较多用于非空判断的 if…else。

实现与示例

传统写法:

String str = “Hello World!”;
if (str != null) {
System.out.println(str);
} else {
System.out.println(“Null”);
}

使用 Optional 之后:

1 Optional strOptional = Optional.of(“Hello World!”);
2 strOptional.ifPresentOrElse(System.out::println, () -> System.out.println(“Null”));

Optional 还有很多方法,这里不一一介绍了。但请注意,不要使用 get() 和 isPresent() 方法,否则和传统的 if…else 无异。

扩展:Kotlin Null Safety

Kotlin 带有一个被称为 Null Safety 的特性:bob?.department?.head?.name

对于一个链式调用,在 Kotlin 语言中可以通过 ?. 避免空指针异常。如果某一环为 null,那整个链式表达式的值便为 null。

方法七:Assert 模式

介绍

上一个方法适用于解决非空检查场景所导致的 if…else,类似的场景还有各种参数验证,比如还有字符串不为空等等。很多框架类库,例如 Spring、Apache Commons 都提供了工具里,用于实现这种通用的功能。这样大家就不必自行编写 if…else 了。

  • Apache Commons Lang 中的 Validate 类:https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/Validate.html
  • Spring 的 Assert 类:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/Assert.html

使用场景

通常用于各种参数校验

扩展:Bean Validation

类似上一个方法,介绍 Assert 模式顺便介绍一个有类似作用的技术 —— Bean Validation。Bean Validation 是 Java EE 规范中的一个。Bean Validation 通过在 Java Bean 上用注解的方式定义验证标准,然后通过框架统一进行验证。也可以起到了减少 if…else 的作用。

方法八:多态

介绍

使用面向对象的多态,也可以起到消除 if…else 的作用。在代码重构这本书中,对此也有介绍:https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html

使用场景

链接中给出的示例比较简单,无法体现适合使用多态消除 if…else 的具体场景。一般来说,当一个类中的多个方法都有类似于示例中的 if…else 判断,且条件相同,那就可以考虑使用多态的方式消除 if…else。

同时,使用多态也不是彻底消除 if…else。而是将 if…else 合并转移到了对象的创建阶段。在创建阶段的 if…,我们可以使用前面介绍的方法处理。

小结

上面这节介绍了 if…else 过多所带来的问题,以及相应的解决方法。除了本节介绍的方法,还有一些其它的方法。

比如,在《重构与模式》一书中就介绍了“用 Strategy 替换条件逻辑”、“用 State 替换状态改变条件语句”和“用 Command 替换条件调度程序”这三个方法。其中的“Command 模式”,其思想同本文的“表驱动”方法大体一致。

另两种方法,因为在《重构与模式》一书中已做详细讲解,这里就不再重复。

何时使用何种方法,取决于面对的问题的类型。上面介绍的一​
些适用场景,只是一些建议,更多的需要开发人员自己的思考。

问题二:if…else 嵌套过深

问题表现

if…else 多通常并不是最严重的的问题。有的代码 if…else 不仅个数多,而且 if…else 之间嵌套的很深,也很复杂,导致代码可读性很差,自然也就难以维护。

if (condition1) {
action1();
if (condition2) {
action2();
if (condition3) {
action3();
if (condition4) {
action4();
}
}
}
}

if…else 嵌套过深会严重地影响代码的可读性。当然,也会有上一节提到的两个问题。

如何解决

上一节介绍的方法也可用用来解决本节的问题,所以对于上面的方法,此节不做重复介绍。这一节重点一些方法,这些方法并不会降低 if…else 的个数,但是会提高代码的可读性:

  1. 抽取方法
  2. 卫语句

方法一:抽取方法

介绍

抽取方法是代码重构的一种手段。定义很容易理解,就是将一段代码抽取出来,放入另一个单独定义的方法。借用 https://refactoring.com/catalog/extractMethod.html 中的定义:

适用场景

if…else 嵌套严重的代码,通常可读性很差。故在进行大型重构前,需先进行小幅调整,提高其代码可读性。抽取方法便是最常用的一种调整手段。

实现与示例

重构前:

public void add(Object element) {
if (!readOnly) {
int newSize = size + 1;
if (newSize > elements.length) {
Object[] newElements = new Object[elements.length + 10];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}

elements = newElements
}
elements[size++] = element;
}
}

重构后:

public void add(Object element) {
if (readOnly) {
return;
}

if (overCapacity()) {
grow();
}

addElement(element);
}

方法二:卫语句

介绍

在代码重构中,有一个方法被称为“使用卫语句替代嵌套条件语句”https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html。直接看代码:

double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else {
if (_isRetired) result = retiredAmount();

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
lse {
if (_isRetired) result = retiredAmount();

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-pypYa8JN-1719101115258)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/734757.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【low-ui-vue】实现原生可扩展动态表格组件

本文字数&#xff1a;3520字 预计阅读时间&#xff1a;20分钟 所谓动态列的表格&#xff0c;就是列数不固定。像广为使用的elementUI的table组件就是表头写死的&#xff0c;这种也叫列数固定的表格。 01 效果 当然&#xff0c;动态性增加了&#xff0c;当然要做出一定“牺牲”。…

delphi 部署设置(deployment)看不见内容的解决方法

情况说明&#xff1a; 这事&#xff0c;今年已遇到两次了&#xff08;分别是两个朋友&#xff09;&#xff0c;情况如下&#xff1a; 菜单&#xff1a;project-->deployment&#xff0c;用于我们对程序部署设置&#xff0c;特别是安卓开发需要使用到。 点开后&#xff0c…

测试辅助工具(抓包工具)的使用4 之 断点

抓包作用3&#xff08;绕过界面限制测试&#xff09; 1.为什么要绕过界面限制做测试&#xff1f; 原因&#xff1a;界面限制导致部分异常数据无法输入 2.如何绕过界面限制做测试&#xff1f; 绕过界面限制直接测试服务器 步骤&#xff1a; 1.设置断点 2.修改请求 3.修改响应…

1panel OpenResty 设置网站重定向

当我们部署网站时需要&#xff0c;输入"cheshi.com"域名回车&#xff0c;希望他自动跳转https://cheshi.com/indx/&#xff0c;而不是直接跳转https://cheshi.com时可以利用重定向来实现&#xff0c; 这里演示的是 1panel 如何设置&#xff08;nginx 貌似也是这样配…

Android中屏蔽 电源键长按、Home键、Home长按

“电源键长按”&#xff08;globalscreen&#xff09; “Home键”&#xff08;homekey&#xff09; “Home长按”&#xff08;recentapps&#xff09; 我们可以使用广播来实现&#xff0c;如&#xff1a; [java] view plain copy print ? package com.jumpinus.test; im…

C# 中的静态关键字

C# 语言中的 static 关键字用于声明静态类和静态类成员。静态类和静态类成员&#xff08;如构造函数、字段、属性、方法和事件&#xff09;在只需要一个对象&#xff08;类或类成员&#xff09;副本并在类型&#xff08;和成员&#xff09;的所有实例&#xff08;对象&#xff…

永磁同步电机FOC调试记录(一)

永磁同步电机FOC调试记录&#xff08;一&#xff09; 前言架构硬件架构软件架构 调试过程元器件选型开环控制编码器调试速度采样电流检测中断优先级的确定电流环部分烧坏IPM速度-电流环位置-电流环 结语 前言 这是我个人从零开始尝试永磁同步电机&#xff08;PMSM&#xff09;…

通用大模型 vs垂直大模型:AI界的“宫斗大戏”

科技圈最近可真热闹&#xff0c;AI大模型的“宫斗大戏”让人眼花缭乱。两个阵营&#xff1a;通用大模型和垂直大模型&#xff0c;正在上演一场激烈的“权力的游戏”。到底谁能笑到最后&#xff1f;咱们一起来“吃瓜”看看吧&#xff01; 首先&#xff0c;登场的是“全能王”通…

leetcode 动态规划(基础版)最长回文字串

题目&#xff1a; 题解&#xff1a; 首先回文子串肯定是连续的&#xff0c;如果用dp来做就需要找出一个串的所有连续子串&#xff0c;枚举一个串所有连续子串的可行方案是首先枚举子串的右端点&#xff0c;范围是&#xff08;0~s.size()-1&#xff09;,在每一个右端点中枚举左…

字节大牛耗时八个月又一力作,Android性能调优秘籍:设计思想与代码质量优化+程序性能优化+开发效率优化(全网疯传)

第一章、设计思想与代码质量优化 一、六大原则 二、设计模式 三、数据结构 四、算法 第二章、 程序性能优化 一、启动速度与执行效率优化 二、 布局检测与优化 三、 内存优化 四、耗电优化 五、网络传输与数据存储优化 六、APK 大小优化 第三章、 开发效率优化 一、…

八大排序浅入浅出

1&#xff09;选择一个增量序列t1&#xff0c;t2&#xff0c;…&#xff0c;tk&#xff0c;其中ti>tj&#xff0c;tk1&#xff1b; 2&#xff09;按增量序列个数k&#xff0c;对序列进行k 趟排序&#xff1b; 3&#xff09;每趟排序&#xff0c;根据对应的增量ti&#xff…

FlinkCDC pipeline模式 mysql-to-paimon.yaml

flinkcdc 需要引入&#xff1a; source端&#xff1a; flink-cdc-pipeline-connector-mysql-xxx.jar、mysql-connector-java-xxx.jar、 sink端&#xff1a; flink-cdc-pipeline-connector-paimon-xxx.jar flinkcdc官方提供connect包下载地址&#xff0c;pipeline模式提交作业和…

Linux查看公网IP的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

STM32单片机BKP备份寄存器和RTC实时时钟详解

文章目录 1. Unix时间戳 2. UTC/GMT 3. 时间戳转换 4. BKP简介 5. BKP基本结构 6. RTC简介 7. RTC框架图 8. RTC基本结构 9. 代码示例 1. Unix时间戳 实时时钟&#xff0c;本质上是一个定时器&#xff0c;专门用来产生年月日时分秒。 Unix 时间戳&#xff08;Unix T…

Apple - Cocoa Event Handling Guide

本文翻译整理自&#xff1a;Cocoa Event Handling Guide&#xff08; https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/Introduction/Introduction.html#//apple_ref/doc/uid/10000060i 文章目录 一、导言本文件的组织另见 二、事件…

如何通过防泄密U盘,实现数据传输的安全性及可控性?

随着信息技术的发展&#xff0c;U盘作为重要的数据存储和传输工具&#xff0c;其安全性越来越受到关注。在日常办公中&#xff0c;经常会遇到这类情况&#xff1a;员工为了方便&#xff0c;随意使用U盘拷贝公司的机密资料。一旦U盘丢失或者被窃取&#xff0c;公司的机密资料就有…

Part 8.2 最短路问题

很多题目都可以转化为最短路的模型。因此&#xff0c;掌握最短路算法非常重要。 >最短路模板< 【模板】全源最短路&#xff08;Johnson&#xff09; 题目描述 给定一个包含 n n n 个结点和 m m m 条带权边的有向图&#xff0c;求所有点对间的最短路径长度&#xff…

vue-Router实现原理

http://localhost:8080/home 三、HashHistory hash("#")的作用是加载 URL 中指示网页中的位置。# 号后面的 hash值&#xff0c;可通过 window.location.hash 获取 特点&#xff1a; hash 不会被包括在 http 请求中&#xff0c;&#xff0c;对服务器端完全无用&…

《黑悟空》抢先版

当《西游记》的古老传说与现代潮流碰撞&#xff0c;一个全新的西游世界在《黑神话悟空》中缓缓展开。你&#xff0c;作为被选中的“天命人”&#xff0c;将踏上一段寻找真相的奇幻旅程。在这里&#xff0c;中国神话的深邃与东方魔幻的绚丽交织&#xff0c;构建出一个令人叹为观…

VBA技术资料MF165:关闭当前打开的所有工作簿

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…