本文首发于 数据浮云: https://mp.weixin.qq.com/s?__...
在写代码的平常中,if...else
语句是极为常见的.正因其常见性,不少同窗在写代码的时候并不会去思考其在目前代码中的用法是否稳当.而随着项目的日渐发展,糟糕的if...else
语句将会充斥在各处,让项目的可维护性急剧降低.故在这篇文章中,笔者想和你们谈谈如何避免写出糟糕if...else
语句.java
因为脱密等缘由.文章中的示例代码将会用一些开源软件的代码或者抽象过的生产代码做为示范.
当咱们看到一组if...else
时,通常是不会有什么阅读负担的.但当咱们看到这样的代码时:mysql
private void validate(APICreateSchedulerMessage msg) { if (msg.getType().equals("simple")) { if (msg.getInterval() == null) { if (msg.getRepeatCount() != null) { if (msg.getRepeatCount() != 1) { throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat more than once")); } } else { throw new ApiMessageInterceptionException(argerr("interval must be set when use simple scheduler when repeat forever")); } } else if (msg.getInterval() != null) { if (msg.getRepeatCount() != null) { if (msg.getInterval() <= 0) { throw new ApiMessageInterceptionException(argerr("interval must be positive integer")); } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() < 0 ) { throw new ApiMessageInterceptionException(argerr("duration time out of range")); } else if ((long) msg.getInterval() * (long) msg.getRepeatCount() * 1000L + msg.getStartTime() > 2147454847000L) { throw new ApiMessageInterceptionException(argerr("stopTime out of mysql timestamp range")); } } } if (msg.getStartTime() == null) { throw new ApiMessageInterceptionException(argerr("startTime must be set when use simple scheduler")); } else if (msg.getStartTime() != null && msg.getStartTime() < 0) { throw new ApiMessageInterceptionException(argerr("startTime must be positive integer or 0")); } else if (msg.getStartTime() != null && msg.getStartTime() > 2147454847 ){ // mysql timestamp range is '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC. // we accept 0 as startDate means start from current time throw new ApiMessageInterceptionException(argerr("startTime out of range")); } if (msg.getRepeatCount() != null && msg.getRepeatCount() <= 0) { throw new ApiMessageInterceptionException(argerr("repeatCount must be positive integer")); } } if (msg.getType().equals("cron")) { if (msg.getCron() == null || ( msg.getCron() != null && msg.getCron().isEmpty())) { throw new ApiMessageInterceptionException(argerr("cron must be set when use cron scheduler")); } if ( (! msg.getCron().contains("?")) || msg.getCron().split(" ").length != 6) { throw new ApiMessageInterceptionException(argerr("cron task must follow format like this : \"0 0/3 17-23 * * ?\" ")); } if (msg.getInterval() != null || msg.getRepeatCount() != null || msg.getStartTime() != null) { throw new ApiMessageInterceptionException(argerr("cron scheduler only need to specify cron task")); } } }
亦或是这样的代码:算法
try { for (int j = myConfig.getContentStartNum(); j <= rowNum; j++) { row = sheet.getRow(j); T obj = target.newInstance(); for (int i = 0; i < colNum; i++) { Field colField = ExcelUtil.getOneByTitle(metaList, titleList[i]); colField.setAccessible(true); String fieldType = colField.getType().getSimpleName(); HSSFCell cell = row.getCell(i); int cellType = cell.getCellType(); System.out.println(colField.getName()+"|"+fieldType+" | "+cellType); if(HSSFCell.CELL_TYPE_STRING == cellType){ if("Date".equals(fieldType)){ colField.set(obj, DateUtil.parse(cell.getStringCellValue())); }else { colField.set(obj, cell.getStringCellValue()); } }else if(HSSFCell.CELL_TYPE_BLANK == cellType){ System.out.println("fieldName"+colField.getName()); if("Boolean".equals(fieldType)){ colField.set(obj, cell.getBooleanCellValue()); }else{ colField.set(obj, ""); } }else if(HSSFCell.CELL_TYPE_NUMERIC == cellType){ if("Integer".equals(fieldType) || "int".equals(fieldType)){ colField.set(obj, (int)cell.getNumericCellValue()); }else { colField.set(obj, cell.getNumericCellValue()); } }else if(HSSFCell.CELL_TYPE_BOOLEAN == cellType){ colField.set(obj, cell.getBooleanCellValue()); } } result.add(obj); } } catch (InstantiationException | IllegalAccessException | ParseException e) { e.printStackTrace(); }
看完这两段代码,相信你们和个人心情是同样的:sql
阅读它们的负担实在是太大了——咱们要记住好几个逻辑判断分支,才能知道到底什么状况下才能获得那个结果.更别说维护的成本有多高了,每次维护时都要读一遍,而后再基于此来改.久而久之,咱们的代码就变成"箭头式代码"了.编程
//............... //............... //............... //............... //............... //............... //............... //............... //............... //...............
前面说过,咱们的目标是减小糟糕的if...else
代码.那么什么是糟糕的if...else
代码呢?咱们能够简单的总结一下:segmentfault
A && B || C
这种.其实这也能够看做变种的嵌套这样就能够看出来,咱们的关键指标就是减小嵌套.windows
三元表达式在代码中也是较为常见的,它能够简化一些if...else
,如:设计模式
public Object getFromOpaque(String key) { return opaque == null ? null : opaque.get(key); }
为何说是一些呢?所以三元表达式必需要有一个返回值.ide
这种状况下就无法使用三元表达式ui
public void putToOpaque(String key, Object value) { if (opaque == null) { opaque = new LinkedHashMap(); } opaque.put(key, value); }
在Java中,switch能够关注一个变量( byte short int 或者 char,从Java7开始支持String),而后在每一个case中比对是否匹配,是的话则进入这个分支.
在一般状况下,switch case
的可读性比起if...else
会好一点.由于if中能够放复杂的表达式,而switch则不行.话虽如此,嵌套起来仍是会很恶心.
所以,若是仅仅是对 byte,short,int和char以String
简单的值判断,能够考虑优先使用switch.
/* 查找年龄大于18岁且为男性的学生列表 */ public ArrayList<Student> getStudents(int uid){ ArrayList<Student> result = new ArrayList<Student>(); Student stu = getStudentByUid(uid); if (stu != null) { Teacher teacher = stu.getTeacher(); if(teacher != null){ ArrayList<Student> students = teacher.getStudents(); if(students != null){ for(Student student : students){ if(student.getAge() > = 18 && student.getGender() == MALE){ result.add(student); } } }else { throw new MyException("获取学生列表失败"); } }else { throw new MyException("获取老师信息失败"); } } else { throw new MyException("获取学生信息失败"); } return result; }
针对这种状况,咱们应该及时抛出异常(或者说return),保证正常流程在外层,如:
/* 查找年龄大于18岁且为男性的学生列表 */ public ArrayList<Student> getStudents(int uid){ ArrayList<Student> result = new ArrayList<Student>(); Student stu = getStudentByUid(uid); if (stu == null) { throw new MyException("获取学生信息失败"); } Teacher teacher = stu.getTeacher(); if(teacher == null){ throw new MyException("获取老师信息失败"); } ArrayList<Student> students = teacher.getStudents(); if(students == null){ throw new MyException("获取学生列表失败"); } for(Student student : students){ if(student.getAge() > 18 && student.getGender() == MALE){ result.add(student); } } return result; }
除了上面的几个tips,咱们还能够经过设计模式来避免写出糟糕的if...else
语句.在这一节,咱们将会提到下面几个设计模式:
在代码中,咱们常常会判断一些业务对象的状态来决定在当前的调用下它该怎么作.咱们举个例子,如今咱们有一个银行的接口:
public interface Bank { /** * 银行上锁 * */ void lock(); /** * 银行解锁 * */ void unlock(); /** * 报警 * */ void doAlarm(); }
让咱们来看一下它的实现类
public class BankImpl implements Bank { @Override public void lock() { //保存这条记录 } @Override public void unlock() { if ((BankState.Day == getCurrentState())) { //白天解锁正常 //仅仅保存这条记录 } else if (BankState.Night == getCurrentState()) { //晚上解锁,可能有问题 //保存这条记录,并报警 doAlarm(); } } @Override public void doAlarm() { if ((BankState.Day == getCurrentState())) { //白天报警,联系当地警方,并保留这条记录 } else if (BankState.Night == getCurrentState()) { //晚上报警,可能有事故,不只联系当地警方,还须要协调附近的安保人员,并保留这条记录 } } private BankState getCurrentState() { return BankState.Day; } }
显然,咱们涉及到了一个状态:
public enum BankState { Day, Night }
在不一样的状态下,同一件事银行可能会做出不一样的反应.这样显然很挫,由于在真实业务场景下,业务的状态可能不只仅只有两种.每多一种,就要多写一个if...else
.因此,若是按照状态模式,能够这样来重构:
public class BankDayImpl implements Bank { @Override public void lock() { //保存这条记录 } @Override public void unlock() { //白天解锁正常 //仅仅保存这条记录 } @Override public void doAlarm() { //白天报警,联系当地警方,并保留这条记录 } }
public class BankNightImpl implements Bank { @Override public void lock() { //保存这条记录 } @Override public void unlock() { //晚上解锁,可能有问题 //保存这条记录,并报警 doAlarm(); } @Override public void doAlarm() { //晚上报警,可能有事故,不只联系当地警方,还须要协调附近的安保人员,并保留这条记录 } }
在本文的第一段的代码中,实际上是ZStack 2.0.5版本中某处的代码,它用来防止用户使用Cli时传入不当的参数,致使后面的逻辑运行不正常.为了方便理解,咱们能够对其规则作一个简化,并画成图的样子来供你们理解.
假设这是一个提交定时重启VM计划任务的“上古级”界面(由于好的交互设计师必定不会把界面设计成这样吧...).规则大概以下:
Simple类型的Scheduler,能够根据Interval
,RepeatCount
,StartTime
来定制一个任务.
Simple
类型的任务时,Interval
,StartTime
这两个参数必填Interval
,和StartTime
,这个时候已经能够提交定时任务了RepeatCount
是个可选参数Cron类型的Scheduler,能够根据cron表达式来提交任务.
在这里请你们思考一个问题,若是要写这样的一个界面,该怎么写?——在一个windows类里,先判断上面的可选栏是哪一种类型,而后根据文本框里的值是否被填好决定提交按钮属否亮起...这算是基本逻辑.上面尚未提到边界值的校验——这些边界值的校验每每会散落在各个组件的实例里,并经过互相通讯的方式来判断本身应该作出什么样的变化,相信你们已经意识到了直接无脑堆if...else
代码的恐怖之处了吧.
接下来,咱们将会贴上来一些伪代码,方便读者更好的理解这个设计模式
/** * 仲裁者的成员接口 * */ public interface Colleague { /** * 设置成员的仲裁者 * */ void setMediator(Mediator mediator); /** * 设置成员是否被启用 * */ void setColleagueEnabled(boolean enabled); }
/** * 仲裁者接口 * */ public interface Mediator { /** * 当一个组员发生状态变化时,调用此方法 * */ void colllectValueChanged(String value); }
/** * 含有textField的组件应当实现接口 */ public interface TextField { String getText(); }
/** * 当一个组件的值发生变化时,ValueListener会收到相应通知 * */ public interface ValueListener { /** * 当组员的值变化时,这个接口会被调用 * */ void valueChanged(String str); }
定义了几个接口以后,咱们开始编写具体的类:
用于表示Simple
和Cron
的checkBox
public class CheckBox { private boolean state; public boolean isState() { return state; } public void setState(boolean state) { this.state = state; } }
Button
public class ColleagueButtonField implements Colleague, ValueListener { private Mediator mediator; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override public void setColleagueEnabled(boolean enabled) { setEnable(enabled); } private void setEnable(boolean enable) { //当true时去掉下划线,并容许被按下 } @Override public void valueChanged(String str) { mediator.colllectValueChanged(str); } }
以及几个Text
public class ColleagueTextField implements Colleague, ValueListener, TextField { private Mediator mediator; private String text; @Override public void setMediator(Mediator mediator) { this.mediator = mediator; } @Override public void setColleagueEnabled(boolean enabled) { setEnable(enabled); } private void setEnable(boolean enable) { //当true时去掉下划线,并容许值输入 } @Override public void valueChanged(String str) { mediator.colllectValueChanged(str); } @Override public String getText() { return text; } }
SchedulerValidator
的具体实现SchedulerValidatorImpl
就不贴上来了,里面仅仅是一些校验逻辑.
接着是咱们的主类,也就是知道全局状态的窗口类
public class MainWindows implements Mediator { private SchedulerValidator validator = new SchedulerValidatorImpl(); ColleagueButtonField submitButton, cancelButton; ColleagueTextField intervalText, repeatCountText, startTimeText, cronText; CheckBox simpleCheckBox, cronCheckBox; public void main() { createColleagues(); } /** * 当一个组员发生状态变化时,调用此方法 * 组件初始化时都为true */ @Override public void colllectValueChanged(String str) { if (simpleCheckBox.isState()) { cronText.setColleagueEnabled(false); simpleChanged(); } else if (cronCheckBox.isState()) { intervalText.setColleagueEnabled(false); repeatCountText.setColleagueEnabled(false); startTimeText.setColleagueEnabled(false); cronChanged(); } else { submitButton.setColleagueEnabled(false); intervalText.setColleagueEnabled(false); repeatCountText.setColleagueEnabled(false); startTimeText.setColleagueEnabled(false); cronText.setColleagueEnabled(false); } } private void cronChanged() { if (!validator.validateCronExpress(cronText.getText())) { submitButton.setColleagueEnabled(false); } } private void simpleChanged() { if (!validator.validateIntervalBoundary(intervalText.getText()) || !validator.validateRepeatCountBoundary(repeatCountText.getText()) || !validator.validateStartTime(startTimeText.getText())) { submitButton.setColleagueEnabled(false); } } private void createColleagues() { submitButton = new ColleagueButtonField(); submitButton.setMediator(this); cancelButton = new ColleagueButtonField(); cancelButton.setMediator(this); intervalText = new ColleagueTextField(); intervalText.setMediator(this); repeatCountText = new ColleagueTextField(); repeatCountText.setMediator(this); startTimeText = new ColleagueTextField(); startTimeText.setMediator(this); cronText = new ColleagueTextField(); cronText.setMediator(this); simpleCheckBox = new CheckBox(); cronCheckBox = new CheckBox(); } }
在这个设计模式中,全部实例状态的判断所有都交给了仲裁者这个实例来判断,而不是互相去通讯.在目前的场景来看,其实涉及的实例还不是特别多,但在一个复杂的系统中,涉及的实例将会变得很是多.假设如今有A,B两个实例,那么会有两条通讯线路:
而有A,B,C时,则有6条线路
这个时候,仲裁者模式的优势就发挥出来了——这些逻辑若是分散在各个角色中,代码将会变得难以维护.
ZStack源码剖析之设计模式鉴赏——三驾马车
结合本文的主题,其实观察者模式作的更多的是将if...else
拆分到属于其本身的模块中.以ZStack的为例,当主存储重连时,主存储模块可能要让模块A和模块B去作一些事,若是不使用观察者模式,那么代码就会都耦合在主存储模块下,拆开if...else
也就不太可能了.
观察者模式通常是经过事件驱动的方式来通讯的,所以Observer和Subject通常都是松耦合的——Subject发出通知时并不会指定消费者.而在以前仲裁者模式的例子中,仲裁者和成员之间紧耦合的(即他们必须互相感知),所以能够考虑经过观察者模式来改进它.
一般在编程时,算法(策略)会被写在具体方法中,这样会致使具体方法中充斥着条件判断语句。可是Strategy却特地将算法与其余部分剥离开来,仅仅定义了接口,而后再以委托的方式来使用算法。然而这种作法正是让程序更加的松耦合(由于使用委托能够方便的总体替换算法),使得整个项目更加茁壮。
ZStack源码剖析之设计模式鉴赏——策略模式
在这篇文章中,笔者和你们分享几个减小if...else
的小tips,因为这些tips都会有必定的限制,所以还向你们介绍了几个可以避免写出糟糕的if...else
的设计模式,并使用观察者模式简单的改进了仲裁者模式的例子.