ifelse是所有高级编程语言必备的特性。但在现实中,代码中的ifelse往往太多了。图片来自Pexels虽然ifelse是必须的,但是滥用ifelse会对代码的可读性和可维护性造成极大的伤害,进而危及整个软件系统。软件开发领域出现了很多新的技术和概念,但是if...else的基本程序形式并没有太大变化。用好ifelse,不仅对现在很有意义,对以后也很有意义。今天我们就来看看如何把代码中的ifelse“干掉”,让代码耳目一新。问题一:ifelse过多的问题ifelse过多的代码可以抽象为如下代码。只列出了5个逻辑分支,但在实际工作中,你可以看到一个方法包含10个、20个甚至更多的逻辑分支。此外,ifelse过多通常还会伴随另外两个问题:复杂的逻辑表达式和ifelse的深度嵌套。对于后两个问题,本文将在接下来的两节中进行介绍。本节首先讨论ifelse过多的情况。if(condition1){}elseif(condition2){}elseif(condition3){}elseif(condition4){}else{}通常,带有太多ifelse的方法通常不可读和可扩展。从软件设计的角度来看,代码中过多的ifelse往往意味着这段代码违反了单一职责原则和开闭原则。因为在实际项目中,需求是经常变化的,新的需求层出不穷。因此,软件系统的可扩展性非常重要。解决ifelse过多的问题,最大的意义往往在于提高代码的可扩展性。如何解决接下来我们看看如何解决ifelse太多的问题。下面我列举一些解决方案:Table-drivenresponsibilitychainmodeAnnotation-drivenevent-drivenfinitestatemachineOptionalAssertPolymorphicmethod1:Table-drivenfixedlogicexpressionmodeifelse代码可以通过表格的形式表达逻辑表达式一定的映射关系;然后用查表的方法找到某个输入对应的处理函数,用这个处理函数进行计算。适用场景:ifelse固定逻辑表达式模式。实现和示例:if(param.equals(value1)){doAction1(someParams);}elseif(param.equals(value2)){doAction2(someParams);}elseif(param.equals(value3)){doAction3(someParams);}//...可以重构为:Map,Function>action>actionMappings=newHashMap<>();//这里的泛型是为了方便演示,其实可以换成你需要的类型//WheninitactionMappings.put(value1,(someParams)->{doAction1(someParams)});actionMappings.put(value2,(someParams)->{doAction2(someParams)});actionMappings.put(value3,(someParams)->{doAction3(someParams)});//省略空判断actionMappings.get(param).apply(someParams);上面的例子使用了Java8的Lambda和FunctionalInterface,这里就不做解释了。表的映射关系可以是集中的也可以是分散的,即每个处理类自己注册。也可以用配置文件来表示。简而言之,有多种形式。还有一些问题,条件表达式不像上面的例子那么简单,但稍微改造一下,也可以应用表驱动。下面借用《编程珠玑》的tax计算例子:ifincome<=2200tax=0elseifincome<=2700tax=0.14*(income-2200)elseifincome<=3200tax=70+0.15*(income-2700)elseifincome<=3700tax=145+0.16*(income-3200)......elsetax=53090+0.7*(income-102200)上面的代码,其实只需要提取税金计算公式,提取每个的标准文件转换成表格,只需添加一个循环。具体重构后的代码就不给出了,大家可以自行思考。方法二:责任链模式当ifelse中的条件表达式灵活多变,条件中的数据不能抽象成一张表统一判断时,则应将条件判断权交给每个功能组件。这些部件以链条的形式串联起来,形成一个完整的功能。适用场景:条件表达式灵活多变,没有统一的形式。实现与示例:责任链模式可以在很多开源框架中Filter和Interceptor功能的实现中看到。让我们看看常见的使用模式。重构前:publicvoidhandle(request){if(handlerA.canHandle(request)){handlerA.handleRequest(request);}elseif(handlerB.canHandle(request)){handlerB.handleRequest(request);}elseif(handlerC.canHandle(request)){handlerC.handleRequest(request);}}重构后:}}publicclassHandlerAextendsHandler{publicvoidhandleRequest(Requestrequest){if(canHandle(request))doHandle(request);elseif(next!=null)next.handleRequest(request);}}当然,示例中重构前的代码是为了表达很明显,一些类和方法已经被提取和重构。实际上,它更像是一种平面代码实现。注:责任链的控制模式,责任链模式在具体实施过程中会有一些不同的表现形式。从链式调用控制的角度来看,可以分为外部控制和内部控制两种。外部控制不灵活,但降低了实施难度。责任链中一个环节的具体实现不需要考虑对下一个环节的调用,因为外部统一控制。但是一般的外部控件也不能实现嵌套调用。如果有嵌套调用,又想从外部控制责任链的调用,实现会稍微复杂一些。具体可以参考SpringWebInterceptor机制的实现方法。内部控制更加灵活,具体实现可以决定是否调用链中的下一个环节。但是如果呼叫控制方式是固定的,那么这样的实现对于用户来说是不方便的。设计模式在具体使用中会有很多变体,需要大家灵活掌握。方法三:注解驱动通过Java注解(或其他语言的类似机制)来定义方法执行的条件。程序执行时,通过比较参与注解中定义的条件是否匹配来判断是否调用该方法。具体实现时,可以采用表驱动或责任链的形式实现。适用场景:适用于条件分支较多,对程序扩展性和易用性要求较高的场景。通常是系统中经常遇到新需求的核心功能。实现与示例:这种模式在很多框架中都可以看到,比如常见的SpringMVC。由于这些框架都非常常用,Demo随处可见,这里不再列出具体的demo代码。这种模式的重点是实施。现有的框架用于实现特定领域的功能,例如MVC。因此,如果业务系统采用这种模式,需要自己实现相关的核心功能。主要涉及反射、责任链等技术。具体实现这里就不演示了。方法四:事件驱动通过关联不同的事件类型和相应的处理机制来实现复杂的逻辑,同时达到解耦的目的。适用场景:从理论上讲,事件驱动可以看作是表驱动的一种,但从实际来看,事件驱动与前面提到的表驱动有很多不同。具体来说:表驱动通常是一对一的关系;事件驱动通常是一对多的。在表驱动中,触发和执行通常是强依赖;在事件驱动中,触发和执行是弱依赖。正是以上两者的不同,导致了两者适用场景的不同。具体可以通过事件驱动触发库存、物流、积分等订单支付完成等功能。实现与示例:在实现上,单机练习驱动可以使用Guava、Spring等框架实现。分布式的一般是通过各种消息队列来实现的。但是因为这里主要讨论的是排除ifelse,所以主要针对单机问题域。由于涉及到具体技术,本模式代码不做演示。方法五:有限状态机有限状态机通常称为状态机(无限状态机的概念可以忽略)。首先引用维基百科上的定义:有限状态机(英文:finite-statemachine,缩写:FSM),简称状态机,是一种数学模型,表示有限数量的状态和这些状态之间的转换和动作等行为.其实状态机也可以看作是表驱动的一种,其实就是当前状态和事件的组合以及处理函数之间的一种对应关系。当然,处理成功后会有一个状态转换过程。适用场景:虽然现在互联网后端服务都在强调无状态,但这并不代表不能使用状态机设计。其实在很多场景下,比如协议栈、订单处理等功能,状态机都有其天然的优势。因为在这些场景中有一个自然状态和状态流。实现与示例:要实现状态机设计,首先需要一个相应的框架。该框架至少需要实现一个状态机定义函数,以及为其实现调用路由函数。可以使用DSL或注释来完成状态机定义。原理并不复杂,掌握了注解、反射等功能的同学应该可以轻松实现。参考技术:①ApacheMina状态机ApacheMina框架,虽然在IO框架领域不如Netty,但提供了状态机功能。自己实现过状态机功能的同学可以参考它的源码:https://mina.apache.org/mina-project/userguide/ch14-state-machine/ch14-state-machine.html②SpringStateMachine有很多Spring子项目,包括一个不显眼的状态机框架,可以通过DSL和注解来定义。https://projects.spring.io/spring-statemachine/以上框架仅供参考。如果涉及到具体的项目,需要根据业务特点自行实现状态机的核心功能。方法六:OptionalJava代码中部分ifelse是非空检查导致的。所以,减少这部分带来的ifelse的个数,也可以减少整体ifelse的个数。Java从8开始就引入了Optional类,用来表示可能为空的对象。这个类提供了很多相关操作的方法,可以用来排除ifelse。开源框架Guava和Scala语言提供了类似的功能。使用场景:非空判断有很多ifelse。实现和例子如下,传统写法:Stringstr="HelloWorld!";if(str!=null){System.out.println(str);}else{System.out.println("Null");}使用Optional之后:Optional
