if else 是我们学习C语言开始用的流程控制语句。还记得大学老师说过一句话,任何复杂的业务逻辑都可以用if else去解决。然而就像面向对象中的继承一样,如果用的过多就会造成代码的腐烂。下面我们就来聊聊if else。
为什么太多的if else不好?
我们先看一个例子:
1 | public void OnMessage(Push.PushMessage pushMessage) { |
这个方法里面仅仅嵌套了好多层if else,看上去会比较复杂难懂,再看一个例子:
1 | if(isSkipPPUForQA && StringUtils.isNotBlank(request.getParameter("userId"))){ |
第一,这个方法中嵌套了七层的if else,层次太多。第二这个方法太长。嵌套层次过多和方法过长都是Bad Smell。那么究竟很多的if else有哪些弊端呢?
僵化:如果再有更多情况的时候,我们需要在原来的地方写更多的if…else if条件。也就是说你需要去改动原来的代码,然后重新编译,重新部署,这是很浪费时间的。并且这违背了面向对象中的开放封闭原则:对扩展开放,对修改封闭。同时由于这个类需要处理各种业务,职责太多,所以也违背了职责单一原则。
效率低下:很多系统的类,比如
HashMaps
,Properties
等,都非常注意基于数据的条件判断。难阅读:像这种层层if else嵌套的情况,如果其他人需要来看,并且维护这份代码,由于难阅读,他们会感觉吃力。试想下,如果段代码很长,一个屏看不完,那肯定是维护的灾难。
难维护:if else不像switch case,它的每个分支都和其它分支有关系,如果需求变更,在修改某个分支之前要看懂其它所有分支,确保不会对其它分支造成影响。
难调试:很多if else,调试过程中需要一步步跟进,会影响调试效率。
难测试:每次我们写测试用例的Case,针对每个有很多if else的方法,我们要对每个分支都写一个测试,这样下来这个测试用例将会变得非常长。
在任何面向对象语言中,都需要考虑移除分支控制逻辑(
if
以及switch
,case
)。移除的常用做法是将这些控制逻辑的方法移到一个类中。 Quora,Simon Hayes
那么我们怎样来解决这种情况,因为遇到不同的情况需要用不同的解决方案,我们逐个来分析:
常见的嵌套类if else处理方式
比如第一个if else的例子,通常
平行类的if else处理方式
如果我们有几个判断条件是平级if,那么我们可以使用命令模式来解决这种问题。比如我们现在有如下的if else:
1 | if (value.equals("A")) { doCommandA() } |
这时,我们可以使用命令模式来解决,先创建一个接口:
1 | public interface Command { |
然后CommandA
和CommandB
类实现这个接口:
1 | public class CommandA() implements Command { |
然后创建一个Map<String,Command>
,并且往其中添加Command实例:
1 | commandMap.put("A", new CommandA()); |
然后所有的if/else if,就都会变成:
1 | commandMap.get(value).exec(); |
如果某个Command有任何的改变只需要改动某个具体的类即可,如果有新加的Command,那么只需要添加响应的Command即可。命令模式就是加了一个中间件:命令容器(就是这里的Map,根据情况可能会是List或者其它)来实现解耦。
复杂处理算法的if else
如果我们if之后的代码处理的业务逻辑很相似,并且这种处理算法可能会经常变动,比如:
1 | public class IfElseDemo { |
那么我们就可以将每个if分支中的代码单独分离到各个类中,然后再抽出一个父类,这样我们每个条件分支中就不会有很多代码了:
1 | public abstract class InsuranceStrategy { |
这样最终不还是有if else吗?是的,最终还是有if else,但是if else的逻辑变得非常清晰,只是用于创建一个新的类。并且我们将经常变化的算法部分封装到了子类中,如果某个子类中的算法变了,只需要变动某个子类(封装变化),然后重新编译就可以了,不需要将整个项目重新编译,部署。
区间类的if else
如果客户端的if条件表示的是不同的范围,然后根据不同范围来选择不同的对象来处理,比如:
1 | public class Client { |
上面这个例子中,不同的条件分支是让不同的对象来处理这种条件。并且以后可能Request对象会添加其他的请求属性,比如offWork(请假),并且这种请求属性同样需要DivisionManager
,Chief
,GeneralManager
。然而其中的处理顺序变了,并不是现在的请求等级。可能是先由Chief
处理,再有GeneralManager
处理,最后有DivisionManager
来处理,那怎么办呢?难道还要写一套if else吗?
这时候我们就可以用责任链模式来将这一长串if else嵌套进每一个对象中去,我们可以这样做:
1 | interface ManagerCommand{ |
最后在Client端调用的时候,我们可以这样写:
1 | public class Client { |
这种写法的好处是:将条件和处理该条件的对象解耦,每个处理条件的对象都不知道其他对象,我们可以随时地增加或者修改处理一个请求的结构。这增加了给对象指派职责的灵活性。
小结:其实上述的每种方式都是利用多态来解决分支带来的僵化,谷歌有一个视频对这个问题阐述得很好。。
参考资料
- https://stackoverflow.com/questions/10175805/how-to-avoid-a-lot-of-if-else-conditions
- https://stackoverflow.com/questions/271526/avoiding-null-statements?rq=1
- https://stackoverflow.com/questions/14136721/converting-many-if-else-statements-to-a-cleaner-approach
- https://www.youtube.com/watch?v=4F72VULWFvc
- https://www.quora.com/Why-should-Java-programmers-try-to-avoid-if-statements
- https://stackoverflow.com/questions/1199646/long-list-of-if-statements-in-swift/1199677#1199677
- https://industriallogic.com/xp/refactoring/conditionalWithStrategy.html
- 大话设计模式
- 重构改善既有代码的设计