前言
if…else 是所有高级编程语言都有的必备性能。但事实中的代码往往存在着过多的 if…else。尽管 if…else 是必须的,但滥用 if…else 会对代码的可读性、可维护性造成很大挫伤,进而危害到整个软件系统。当初软件开发畛域呈现了很多新技术、新概念,但 if…else 这种根本的程序模式并没有产生太大变动。应用好 if…else 不仅对于当初,而且对于未来,都是非常有意义的。明天咱们就来看看如何“干掉”代码中的 if…else,还代码以清新。
问题一:if…else 过多
问题体现
if…else 过多的代码能够形象为上面这段代码。其中只列出 5 个逻辑分支,但理论工作中,能见到一个办法蕴含 10 个、20 个甚至更多的逻辑分支的状况。另外,if…else 过多通常会随同着另两个问题:逻辑表达式简单和 if…else 嵌套过深。对于后两个问题,本文将在上面两节介绍。本节先来探讨 if…else 过多的状况。
if (condition1) {} else if (condition2) {} else if (condition3) {} else if (condition4) {} else {}
通常,if…else 过多的办法,通常可读性和可扩展性都不好。从软件设计角度讲,代码中存在过多的 if…else 往往意味着这段代码违反了违反繁多职责准则和开闭准则。因为在理论的我的项目中,需要往往是一直变动的,新需要也层出不穷。所以,软件系统的扩展性是十分重要的。而解决 if…else 过多问题的最大意义,往往就在于进步代码的可扩展性。
如何解决
接下来咱们来看如何解决 if…else 过多的问题。上面我列出了一些解决办法。
-
- 表驱动
-
- 职责链模式
-
- 注解驱动
-
- 事件驱动
-
- 无限状态机
-
- Optional
-
- Assert
-
- 多态
办法一:表驱动
介绍
对于逻辑表白模式固定的 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。故业务零碎如采纳此模式需自行实现相干外围性能。次要会波及反射、职责链等技术。具体的实现这里就不做演示了。
办法四:事件驱动
介绍
通过关联不同的事件类型和对应的解决机制,来实现简单的逻辑,同时达到解耦的目标。
实用场景
从实践角度讲,事件驱动能够看做是表驱动的一种,但从实际角度讲,事件驱动和后面提到的表驱动有多处不同。具体来说:
- 表驱动通常是一对一的关系;事件驱动通常是一对多;
- 表驱动中,触发和执行通常是强依赖;事件驱动中,触发和执行是弱依赖
正是上述两者不同,导致了两者实用场景的不同。具体来说,事件驱动可用于如订单领取实现触发库存、物流、积分等性能。
实现与示例
实现形式上,单机的实际驱动能够应用 Guava、Spring 等框架实现。分布式的则个别通过各种音讯队列形式实现。然而因为这里次要探讨的是打消 if…else,所以次要是面向单机问题域。因为波及具体技术,所以此模式代码不做演示。
办法五:无限状态机
介绍
无限状态机通常被称为状态机(有限状态机这个概念能够疏忽)。先援用维基百科上的定义:
无限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是示意无限个状态以及在这些状态之间的转 > 移和动作等行为的数学模型。
其实,状态机也能够看做是表驱动的一种,其实就是以后状态和事件两者组合与处理函数的一种对应关系。当然,解决胜利之后还会有一个状态转移解决。
实用场景
尽管当初互联网后端服务都在强调无状态,但这并不意味着不能应用状态机这种设计。其实,在很多场景中,如协定栈、订单解决等性能中,状态机有这其人造的劣势。因为这些场景中人造存在着状态和状态的流转。
实现与示例
实现状态机设计首先须要有相应的框架,这个框架须要实现至多一种状态机定义性能,以及对于的调用路由性能。状态机定义能够应用 DSL 或者注解的形式。原理不简单,把握了注解、反射等性能的同学应该能够很容易实现。
参考技术:
- Apache Mina State Machine
Apache Mina 框架,尽管在 IO 框架畛域不迭 Netty,但它却提供了一个状态机的性能。https://mina.apache.org/mina-…。有本人实现状态机性能的同学能够参考其源码。 - Spring State Machine
Spring 子项目泛滥,其中有个不显山不露水的状态机框架 —— Spring State Machine https://projects.spring.io/sp…。能够通过 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<String> 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/pr…
- Spring 的 Assert 类:https://docs.spring.io/spring…
应用场景
通常用于各种参数校验
扩大:Bean Validation
相似上一个办法,介绍 Assert 模式顺便介绍一个有相似作用的技术 —— Bean Validation。Bean Validation 是 Java EE 标准中的一个。Bean Validation 通过在 Java Bean 上用注解的形式定义验证规范,而后通过框架对立进行验证。也能够起到了缩小 if…else 的作用。
办法八:多态
介绍
应用面向对象的多态,也能够起到打消 if…else 的作用。在代码重构这本书中,对此也有介绍:
https://refactoring.com/catal…
应用场景
链接中给出的示例比较简单,无奈体现适宜应用多态打消 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 的个数,然而会进步代码的可读性:
- 抽取办法
- 卫语句
办法一:抽取办法
介绍
抽取办法是代码重构的一种伎俩。定义很容易了解,就是将一段代码抽取进去,放入另一个独自定义的办法。借
用 https://refactoring.com/catal… 中的定义:
实用场景
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/catal…。间接看代码:
double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {if (_isSeparated) result = separatedAmount();
else {if (_isRetired) result = retiredAmount();
else result = normalPayAmount();};
}
return result;
}
重构之后
double getPayAmount() {if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();}
应用场景
当看到一个办法中,某一层代码块都被一个 if…else 残缺管制时,通常能够采纳卫语句。
问题三:if…else 表达式过于简单
问题体现
if…else 所导致的第三个问题来自过于简单的条件表达式。上面给个简略的例子,当 condition 1、2、3、4 别离为 true、false,请大家排列组合一下上面表达式的后果。
1 if ((condition1 && condition2) || ((condition2 || condition3) && condition4)) {
2
3 }
我想没人违心干下面的事件。要害是,这一大坨表达式的含意是什么?要害便在于,当不晓得表达式的含意时,没人违心推断它的后果。
所以,表达式简单,并不一定是错。然而表达式难以让人了解就不好了。
如何解决
对于 if…else 表达式简单的问题,次要用代码重构中的抽取办法、挪动办法等伎俩解决。因为这些办法在《代码重构》一书中都有介绍,所以这里不再反复。
总结
本文一个介绍了 10 种(算上扩大有 12 种)用于打消、简化 if…else 的办法。还有一些办法,如通过策略模式、状态模式等伎俩打消 if…else 在《重构与模式》一书中也有介绍。
正如前言所说,if…else 是代码中的重要组成部分,然而适度、不必要地应用 if…else,会对代码的可读性、可扩展性造成负面影响,进而影响到整个软件系统。
“干掉”if…else 的能力高下反映的是程序员对软件重构、设计模式、面向对象设计、架构模式、数据结构等多方面技术的综合使用能力,反映的是程序员的内功。要正当应用 if…else,不能没有设计,也不能适度设计。这些对技术的综合、正当地使用都须要程序员在工作中一直的摸索总结。
作者:艾瑞克·邵
起源:www.cnblogs.com/eric-shao/p/10115577.html
欢送关注公众号【码农开花】一起学习成长
我会始终分享 Java 干货,也会分享收费的学习材料课程和面试宝典
回复:【计算机】【设计模式】【002】有惊喜哦