`

过多if-else分支的优化

阅读更多

我想谈一谈这个话题是因为我的上一篇博客在ITEye上有一些朋友回复,说if-else过多的分支可以使用switch或者责任链模式等等方式来优化。确实,这是一个小问题,不过我们还是可以整理一下这个小问题的重构方式。

为什么要优化?

你没有看错。这是要放在第一条谈论的。

有许多人会说,叠起来一堆if-else分支,代码就不优雅了。可是,怎样去定义“优雅”的概念呢?再退一步说,即便不“优雅”,又有什么问题?

对于这样一段再普通不过的代码:

1
2
3
4
5
6
7
8
int code;
if("Name".equals(str))
    code = 0;
else if("Age".equals(str))
    code = 1;
else if("Address".equals(str))
    code = 2;
...

可以有好多种重构方式,但是使用这样的代码,虽然简陋,但在大多数情况下,并不会影响什么,比如,对可维护性没有影响。当然,如果你发现其中确有不好的一面,那就要考虑重构它。换言之,通常你首先要说出某段代码的问题(比如,你觉得这段代码不符合开闭原则,因为你希望保持这段代码闭合稳定),那么才去存在重构的必要,而不要总是使用“优雅”和“简洁”搪塞疑问。几乎所有的书上都说要写出优雅的、简洁的代码,这本身无可厚非,但是事物需要使用自己的判断,可不要被习惯性地洗了脑。

在我前一家公司,是典型的通讯和传统软件的公司,代码质量普遍不错,但是很多时候,会看到许许多多不够优雅的代码——也许你觉得不够简洁、美观,但是下代码严谨、清晰,我觉得这就很好。反之,某一些精巧的设计,可能会带来可阅读性和可理解性下降的问题。

寻找代替分支判断的方式

接下去我们再来考虑怎么样去重构优化过多的if-else分支。

程序逻辑最基本的组成就是分支、判断和循环。而过多if-else正是由于在某一个变化的点上,有许多判断条件和结果分支造成的。所以最基本的解决办法就是把多个判断条件合成一个,也就是把若干个分支合成一个。

但是在大多数情况下,条件判断的分支都是无法合并的。所以,我们需要把这个变化点通过别的途径封装起来,而不是采用if-else。

1. 用一个Map可以做到,if-else的变化点使用Map的get方法来代替:

1
2
3
4
5
6
Map typeCodeMap = new HashMap();
typeCodeMap.put("Name", 0);
typeCodeMap.put("Age", 1);
typeCodeMap.put("Address", 2);
...
int code = typeCode.get(type);

2. 枚举:

1
2
3
4
5
6
7
8
9
10
11
public enum Codes {
    Name(0), Age(1), Address(2);
     
    public int code;
    Codes(int code){
        this.code = code;
    }
}
 
//使用:
int code = Codes.valueOf(str).code;

3. 多态:

1
2
ICode iCode = (ICode)Class.forName("com.xxx." + str).newInstance();
int code = iCode.getCode();

当然,如果仅考虑从String转向int这样的转换,用这样的方式来简化分支判断逻辑,这个方式、这个例子不是很恰当。当然,这样的方式经常被用来做从字符串到具体对象的转换。

还有一些朋友说的这个模式那个模式来解决多if-else的问题,这些都是正确的,当然本质上也无一例外基于多态来实现的,所以我就不提及了。这些都不错,至少比那些老说用switch来代替if-else的有价值多了 :)

最后,对于如此小的一个问题,我要补充说明的一点是,看不得大片if-else和看不得大片new关键字一样,我觉得这是许多Java程序员的既有观念或者说习惯,甚至通病——这并不好。Java最有价值的地方不是它的语义语法也不是它的虚拟机跨平台和有多高性能,而在于它的社区它的无比丰富的类库,在于使用它的人可以从设计上和宏观上去思考问题。但是Java程序员,也包括我在内,很容易把这条路走得过于极端,比如遍地的Factory,比如漫山遍野的配置,比如永远也不会被复用的可复用代码,比如永远也不会被扩展的可扩展代码,还比如从前到后由内到外的分层,一层又一层。相对于这些方面无止境的追求,我们还是专注于要解决的问题,多写一些清晰可用的代码吧。

 

文章系本人原创,转载请保持完整性并注明出自《四火的唠叨》

20
5
分享到:
评论
23 楼 冲杯茶喝 2013-02-28  
lazy_ 写道
kidneyball 写道
赞成楼主“不要看到一堆if就想优化”的观点。但对楼主的论证过程有异议:

首先那位朋友的问题是“一堆if怎么优化”(其实应该说怎么重构提高可读性,一堆if如果能满足需求,那已经是最优的了),他没有问怎样的if需要优化。因此我觉得应该假设他已经判断过是否应该重构,但不知道具体手段。至于他怎么判断出来的,我们不知道,但不应该假设他肯定是因为“优雅强迫症”而决定重构。

其次感觉楼主举的例子没有切中要点。用查表代替硬编码的赋值是一种很常见的做法,但硬编码赋值只是if的一种非常特殊的用法。特殊到只要超过三四个分支,一般人都会想到用查表来代替,导致在实际场景根本不会有这样一段代码等着你去重构。因此我觉得应该假定那位朋友问的是“对于复杂的if分支中存在复杂处理的情况如何重构”

我觉得要正面回答这个问题,可以先不要管我回复的责任链之类的大词,直接来一步步看这种情况该怎么重构。

首先,既然每个if分支后面跟着一堆复杂的逻辑,每个分支里做的事情肯定不同,把它们堆在一个方法中并不妥当(如果楼主想讨论为什么不妥当,可以另外讨论,最常见的不妥当是因为各个分支的抽象层次不同导致阅读者思路混乱)。因此最好把各个分支的处理代码分别抽出来,分别形成独立的方法。这样每个分支处理都有明确的边界,而且我们可以在方法上写javadoc,形成良好的文档。

好,现在你有了一个单纯含有if分支的主控方法和一堆执行处理的方法,你面临的第二个问题是每个方法上都要传入一大串参数,因为原来的复杂处理往往依赖大量的上下文状态。解决这个问题的最正统有效(未必优雅)的方法是创建一个上下文(Context)类,或者也可以叫请求(Request)类来携带传入参数。这样可以解决参数文档问题,默认值问题和参数顺序问题。但你有许多个执行方法,显然你不会傻到为每个方法都创建一个上下文,而会只创建一个上下文类,每个方法都接收这个上下文实例,只取自己真正关注的属性。毕竟既然这些执行方法都从一个if结构中抽取处理,这些属性之间逻辑上总有些关联,放在一起也不会有很大问题。

现在你有了一堆参数一致,但名称不同的方法。如果你的需求变动不大,就这样就可以了。但如果你觉得需求可能会有变化,未来可能需要覆盖其中一些方法。你会发现,如果需求1需要你覆盖A,你需要创建一个子类。需求2需要你覆盖B,又要创建一个子类。需求3需要你同时具有需求A,需求B的特性,你又要创建一个子类。既然这样,何不把它们抽到独立的类中,可以分别扩展? 抽取过程中,你发现现在每个处理类都只有一个方法,方法名和类名是重复的。而且本质上它们都是某种处理器(Handler),何不让它们实现统一的接口,方法名统一改为handle。强调一下,这一步是预期需求会有变化的情况才做,如果认为需求不太可能会变化,或者预计变化有足够时间重构,完全可以在前一步就停止。

好,现在你有一个主控方法,这个方法创建一个上下文对象,再根据分支条件分别调用不同Handler子类上的handle方法,传入这个上下文。你注意到一个问题,分支条件本身和对应的处理逻辑是内聚的。如果条件发生变化,处理往往也要发生变化。反之依然。而且你读代码时,读到一个复杂的条件,往往不能轻易看出它要判断什么,这时最好的方法就是直接看看对应Handler的命名和文档,从处理方式反推这个条件对应的业务需求。既然这样,何不干脆把条件都搬到Handler里去,让每个Handler根据传入的上下文,看看在当前状态下自己是否应该执行处理。

现在你得到了一个主控类,这个类持有一堆Handler实例,主控类创建一个上下文,然后把上下文依次传给各个Handler,Handler自行判断是否应该执行自己的处理。

到了这一步,其实已经差不多了。不过对于某些人,他在进行前一步的重构时,就会醒悟:主控类现在已经变成了一个单纯的任务转发人(分配者)。它根本没有必要持有一个Handler的列表再分别逐个调用,还要管理该继续还是该中断等等逻辑(这些逻辑是依赖每个Handler返回的标志来决定的)。何不让Handler自己负责把控制向后分发,主控类只需要知道领头的那个Handler最终会把事情处理好就行了。这种结构还有一个好处,就是每个Handler可以自行决定是否该往下传递控制,还可以根据需要替换上下文实例的实现来影响后续的处理。(这一步与上一步是二选一,有些人喜欢在主控类中持有Handler队列,有些人喜欢链式Handler。我个人认为问题不大,两者的实现难度也没有差别,实现需求就行)

最后,我们为了交流方便,把这种组合方式称为“责任链”。





大致明白了你的意思。你的意思是,主控类把参数封装为context,然后依次传递到责任链上的handlers。每一个handler都尝试去处理(判断是否符合if条件),如果不能,则委托责任链的下一个handler。按这种方式实现的话,每一个handler都只有一个if/else而已。

妙哉!


Netty的ChannelPipeline正是用的链式Handler!
22 楼 rainsilence 2013-02-27  
rainsilence 写道
RayChase 写道
kidneyball 写道
留下的祝福 写道
正好我碰到,这里如何把那么多if去掉:
List<TWorkFlowwork> wfwList=errorProcessingService.findWorkFlowworkByWorkType("7",workbillcode.getId()+"");
								boolean flag=false;
								if(wfwList!=null&&wfwList.size()>0){
									for(int i=0;i<wfwList.size();i++){
										TWorkFlowwork wfw=wfwList.get(i);
										if(wfw!=null){//当前待办是原件校验并且如果已经结束了则激活原件校验待办
											if(wfw.getStatus()!=null&&!"".equals(wfw.getStatus())){
												if("1".equals(wfw.getStatus())){
													flag=true;
													break;
												}
											}
											
										}
									}
									if(!flag){//如果原件校验待办都结束了,则产生一条原件校验待办
										TWorkBillcode wb=errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
										wb.setIsmatchpage("1");
										errorProcessingService.updateWorkBillcode(wb);
									}
								}



private void foo() {
	List<TWorkFlowwork> wfwList= errorProcessingService.findWorkFlowworkByWorkType("7", workbillcode.getId()+"");
	if (wfwList == null) return;
	//如果原件校验待办都结束了,则产生一条原件校验待办
	if (allFinished(wfwList)) {
		TWorkBillcode wb = errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
		wb.setIsmatchpage("1");
		errorProcessingService.updateWorkBillcode(wb);
	}
}

private boolean allFinished(List<TWorkFlowwork> wfwList) {
	for (TWorkFlowwork wfw : wfwList) {
		if (wfw != null && "1".equals(wfw.getStatus())) return false;
	}
	return true;
}



这个重构真棒。


可惜是错的

我看错了。。
21 楼 rainsilence 2013-02-27  
RayChase 写道
kidneyball 写道
留下的祝福 写道
正好我碰到,这里如何把那么多if去掉:
List<TWorkFlowwork> wfwList=errorProcessingService.findWorkFlowworkByWorkType("7",workbillcode.getId()+"");
								boolean flag=false;
								if(wfwList!=null&&wfwList.size()>0){
									for(int i=0;i<wfwList.size();i++){
										TWorkFlowwork wfw=wfwList.get(i);
										if(wfw!=null){//当前待办是原件校验并且如果已经结束了则激活原件校验待办
											if(wfw.getStatus()!=null&&!"".equals(wfw.getStatus())){
												if("1".equals(wfw.getStatus())){
													flag=true;
													break;
												}
											}
											
										}
									}
									if(!flag){//如果原件校验待办都结束了,则产生一条原件校验待办
										TWorkBillcode wb=errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
										wb.setIsmatchpage("1");
										errorProcessingService.updateWorkBillcode(wb);
									}
								}



private void foo() {
	List<TWorkFlowwork> wfwList= errorProcessingService.findWorkFlowworkByWorkType("7", workbillcode.getId()+"");
	if (wfwList == null) return;
	//如果原件校验待办都结束了,则产生一条原件校验待办
	if (allFinished(wfwList)) {
		TWorkBillcode wb = errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
		wb.setIsmatchpage("1");
		errorProcessingService.updateWorkBillcode(wb);
	}
}

private boolean allFinished(List<TWorkFlowwork> wfwList) {
	for (TWorkFlowwork wfw : wfwList) {
		if (wfw != null && "1".equals(wfw.getStatus())) return false;
	}
	return true;
}



这个重构真棒。


可惜是错的
20 楼 361010911 2013-02-27  
可以试试用Polling!
1.
interface CoreService{
public boolean condition(String name);
public Integer getCode();
}
2.
Name implements CoreService{
public boolean condition(String name){
    return name.equals("Name");
}
public Integer getCode(){
    return 0;
}
}
3.
Age implements CoreService{
public boolean condition(String name){
    return name.equals("Age");
}
public Integer getCode(){
    return 1;
}
}
4.
class CoreCombination {
private List<CoreService> coreServices;
public CoreCombination{
  coreServices=new arrayList<CoreService>();
  coreServices.add(new Name());
  coreServices.add(new Age());
}
public String getCode(String name){
  for(CoreService coreService : coreServices){
     if(coreService.condition(name)){
      return coreService.getCode();
     }
  }
  return null;
}
}

5.Test
CoreCombination coreCombination = new CoreCombination();
System.out.println("Your code is "+coreCombination.getCode('Name'));
19 楼 RayChase 2013-02-26  
kidneyball 写道
留下的祝福 写道
正好我碰到,这里如何把那么多if去掉:
List<TWorkFlowwork> wfwList=errorProcessingService.findWorkFlowworkByWorkType("7",workbillcode.getId()+"");
								boolean flag=false;
								if(wfwList!=null&&wfwList.size()>0){
									for(int i=0;i<wfwList.size();i++){
										TWorkFlowwork wfw=wfwList.get(i);
										if(wfw!=null){//当前待办是原件校验并且如果已经结束了则激活原件校验待办
											if(wfw.getStatus()!=null&&!"".equals(wfw.getStatus())){
												if("1".equals(wfw.getStatus())){
													flag=true;
													break;
												}
											}
											
										}
									}
									if(!flag){//如果原件校验待办都结束了,则产生一条原件校验待办
										TWorkBillcode wb=errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
										wb.setIsmatchpage("1");
										errorProcessingService.updateWorkBillcode(wb);
									}
								}



private void foo() {
	List<TWorkFlowwork> wfwList= errorProcessingService.findWorkFlowworkByWorkType("7", workbillcode.getId()+"");
	if (wfwList == null) return;
	//如果原件校验待办都结束了,则产生一条原件校验待办
	if (allFinished(wfwList)) {
		TWorkBillcode wb = errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
		wb.setIsmatchpage("1");
		errorProcessingService.updateWorkBillcode(wb);
	}
}

private boolean allFinished(List<TWorkFlowwork> wfwList) {
	for (TWorkFlowwork wfw : wfwList) {
		if (wfw != null && "1".equals(wfw.getStatus())) return false;
	}
	return true;
}



这个重构真棒。
18 楼 RayChase 2013-02-26  
runfriends 写道
对于易于抽象的大段逻辑,使用责任链是非常好的。
不过很多情况下,if-else判断太多,嵌套太多,执行的逻辑又很琐碎,把它们用责任链实现要实现好多的类,花了很多时间,代码量增加了很多。把这些琐碎的逻辑,分别放到不同的类里实现一个类几行代码,是不是值得?

过多的抽象和封装是否会降低代码的可读性和可维护性,在逻辑很琐碎的情况下,与使用大段的if-else相比哪种方式有更好的可读性和可维护性呢?


是的。很多时候大段的if-else未必不好,还是要看具体问题。大多时候都是在权衡而已。
17 楼 taoge2121 2013-02-26  
不是能把if去掉就应该去掉,哎……
16 楼 anhui3713 2013-02-25  
我最常用的就是map那种了...只是觉得效率上其实不太高
15 楼 jjcang 2013-02-25  
do{
....
while(false);
14 楼 lazy_ 2013-02-25  
kidneyball在这两篇文章的回复太精彩了,让我有种看effective java的感觉!
13 楼 kidneyball 2013-02-25  
runfriends 写道
对于易于抽象的大段逻辑,使用责任链是非常好的。
不过很多情况下,if-else判断太多,嵌套太多,执行的逻辑又很琐碎,把它们用责任链实现要实现好多的类,花了很多时间,代码量增加了很多。把这些琐碎的逻辑,分别放到不同的类里实现一个类几行代码,是不是值得?

过多的抽象和封装是否会降低代码的可读性和可维护性,在逻辑很琐碎的情况下,与使用大段的if-else相比哪种方式有更好的可读性和可维护性呢?


我的看法是,如果一个复杂if-else结构所涉及的条件是业务需求带来的,我倾向于把它做成责任链,因为这样一个复杂的分支结构说明这里有一个比较重要的业务决策点。一般来说,一个系统如果设计得好的话,纯粹由业务需求形成复杂if嵌套的地方不会太多,别忘了我们有很多其他方法(例如把常用判断抽成工具方法、使用策略模式等等)消除重复出现的if结构。而且责任链实现起来也很方便,整个系统可以使用同一的责任链基类,具体子类只要实现handle方法就行,如果没有明确的复用需要,做成私有静态内部类就行了。这样以后如果需要复用可以随时抽出去。等将来Java8流行起来,可以用Lambda表达式来写,就更方便了。

另一方面,只要能足够肯定分支逻辑不会有太大变动,各个分支的处理也很简单,保留if-else也很直观。如果嵌套得太深,或者条件太复杂影响阅读,可以考虑以下一些手段来改写:

1. 超过三层的嵌套,可以考虑把内部嵌套抽成独立方法。一般来说,超过三层的嵌套,阅读代码时已经很难直观地通过阅读条件来理解对应的需求了。这时阅读者往往需要根据处理逻辑反推条件的含义,所以即使内部处理还是一个if-else分支,你也应该考虑加点注释说明这里的处理逻辑在需求层面的实际含义,方便读者理解前面的条件。但与其写内部注释(我个人很不喜欢在方法中写内部注释),不如干脆就把这部分代码抽出来形成独立的方法,起个好点的名字。

2. 合理使用return。虽然有些观点说一个方法只有一个出口比较好。但我在实践中没有发现这种做法有任何实质性的好处,反而无谓地增加了代码嵌套层数。可能这种观点的背景是长函数盛行的结构化编程年代。只要方法足够简短,适当使用return可以有效减少if的嵌套层数,使方法更加易读。特别是在方法开头就进行一些判断直接return,是在各种开源项目中都非常常见的做法。

3. 注意不要做无谓的判断。最常见是无用的非空判断,经常会看到在同一个方法里,之前已经直接调用过某个对象变量上的方法,然后这个变量一直没有修改,后面还在做这个变量的非空判断。如果这个变量为空早就抛空指针了。空指针判断要做的话,尽量在方法早期做。尽量判断为空后进行分支处理,要么抛异常,要么return,要么设默认值。主线逻辑可以写在if外部。

4. 如果一个if里的条件比较复制,可以考虑把条件单独抽成局部变量或独立方法。多个连续嵌套的if可能的话尽量用&&或||代替。

5. 如果用Intellij的话,IDE提供了自动合并多个嵌套if, 简化if(自动去掉恒真或恒假的条件,或者把分支内返回布尔值的if转换为三元表达式等等), 复杂条件自动加括号,条件取反等功能。可以尝试重构一下看看哪种表达方式更易读。
12 楼 runfriends 2013-02-25  
对于易于抽象的大段逻辑,使用责任链是非常好的。
不过很多情况下,if-else判断太多,嵌套太多,执行的逻辑又很琐碎,把它们用责任链实现要实现好多的类,花了很多时间,代码量增加了很多。把这些琐碎的逻辑,分别放到不同的类里实现一个类几行代码,是不是值得?

过多的抽象和封装是否会降低代码的可读性和可维护性,在逻辑很琐碎的情况下,与使用大段的if-else相比哪种方式有更好的可读性和可维护性呢?
11 楼 kidneyball 2013-02-25  
留下的祝福 写道
正好我碰到,这里如何把那么多if去掉:
List<TWorkFlowwork> wfwList=errorProcessingService.findWorkFlowworkByWorkType("7",workbillcode.getId()+"");
								boolean flag=false;
								if(wfwList!=null&&wfwList.size()>0){
									for(int i=0;i<wfwList.size();i++){
										TWorkFlowwork wfw=wfwList.get(i);
										if(wfw!=null){//当前待办是原件校验并且如果已经结束了则激活原件校验待办
											if(wfw.getStatus()!=null&&!"".equals(wfw.getStatus())){
												if("1".equals(wfw.getStatus())){
													flag=true;
													break;
												}
											}
											
										}
									}
									if(!flag){//如果原件校验待办都结束了,则产生一条原件校验待办
										TWorkBillcode wb=errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
										wb.setIsmatchpage("1");
										errorProcessingService.updateWorkBillcode(wb);
									}
								}



private void foo() {
	List<TWorkFlowwork> wfwList= errorProcessingService.findWorkFlowworkByWorkType("7", workbillcode.getId()+"");
	if (wfwList == null) return;
	//如果原件校验待办都结束了,则产生一条原件校验待办
	if (allFinished(wfwList)) {
		TWorkBillcode wb = errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
		wb.setIsmatchpage("1");
		errorProcessingService.updateWorkBillcode(wb);
	}
}

private boolean allFinished(List<TWorkFlowwork> wfwList) {
	for (TWorkFlowwork wfw : wfwList) {
		if (wfw != null && "1".equals(wfw.getStatus())) return false;
	}
	return true;
}

10 楼 lazy_ 2013-02-25  
kidneyball 写道
赞成楼主“不要看到一堆if就想优化”的观点。但对楼主的论证过程有异议:

首先那位朋友的问题是“一堆if怎么优化”(其实应该说怎么重构提高可读性,一堆if如果能满足需求,那已经是最优的了),他没有问怎样的if需要优化。因此我觉得应该假设他已经判断过是否应该重构,但不知道具体手段。至于他怎么判断出来的,我们不知道,但不应该假设他肯定是因为“优雅强迫症”而决定重构。

其次感觉楼主举的例子没有切中要点。用查表代替硬编码的赋值是一种很常见的做法,但硬编码赋值只是if的一种非常特殊的用法。特殊到只要超过三四个分支,一般人都会想到用查表来代替,导致在实际场景根本不会有这样一段代码等着你去重构。因此我觉得应该假定那位朋友问的是“对于复杂的if分支中存在复杂处理的情况如何重构”

我觉得要正面回答这个问题,可以先不要管我回复的责任链之类的大词,直接来一步步看这种情况该怎么重构。

首先,既然每个if分支后面跟着一堆复杂的逻辑,每个分支里做的事情肯定不同,把它们堆在一个方法中并不妥当(如果楼主想讨论为什么不妥当,可以另外讨论,最常见的不妥当是因为各个分支的抽象层次不同导致阅读者思路混乱)。因此最好把各个分支的处理代码分别抽出来,分别形成独立的方法。这样每个分支处理都有明确的边界,而且我们可以在方法上写javadoc,形成良好的文档。

好,现在你有了一个单纯含有if分支的主控方法和一堆执行处理的方法,你面临的第二个问题是每个方法上都要传入一大串参数,因为原来的复杂处理往往依赖大量的上下文状态。解决这个问题的最正统有效(未必优雅)的方法是创建一个上下文(Context)类,或者也可以叫请求(Request)类来携带传入参数。这样可以解决参数文档问题,默认值问题和参数顺序问题。但你有许多个执行方法,显然你不会傻到为每个方法都创建一个上下文,而会只创建一个上下文类,每个方法都接收这个上下文实例,只取自己真正关注的属性。毕竟既然这些执行方法都从一个if结构中抽取处理,这些属性之间逻辑上总有些关联,放在一起也不会有很大问题。

现在你有了一堆参数一致,但名称不同的方法。如果你的需求变动不大,就这样就可以了。但如果你觉得需求可能会有变化,未来可能需要覆盖其中一些方法。你会发现,如果需求1需要你覆盖A,你需要创建一个子类。需求2需要你覆盖B,又要创建一个子类。需求3需要你同时具有需求A,需求B的特性,你又要创建一个子类。既然这样,何不把它们抽到独立的类中,可以分别扩展? 抽取过程中,你发现现在每个处理类都只有一个方法,方法名和类名是重复的。而且本质上它们都是某种处理器(Handler),何不让它们实现统一的接口,方法名统一改为handle。强调一下,这一步是预期需求会有变化的情况才做,如果认为需求不太可能会变化,或者预计变化有足够时间重构,完全可以在前一步就停止。

好,现在你有一个主控方法,这个方法创建一个上下文对象,再根据分支条件分别调用不同Handler子类上的handle方法,传入这个上下文。你注意到一个问题,分支条件本身和对应的处理逻辑是内聚的。如果条件发生变化,处理往往也要发生变化。反之依然。而且你读代码时,读到一个复杂的条件,往往不能轻易看出它要判断什么,这时最好的方法就是直接看看对应Handler的命名和文档,从处理方式反推这个条件对应的业务需求。既然这样,何不干脆把条件都搬到Handler里去,让每个Handler根据传入的上下文,看看在当前状态下自己是否应该执行处理。

现在你得到了一个主控类,这个类持有一堆Handler实例,主控类创建一个上下文,然后把上下文依次传给各个Handler,Handler自行判断是否应该执行自己的处理。

到了这一步,其实已经差不多了。不过对于某些人,他在进行前一步的重构时,就会醒悟:主控类现在已经变成了一个单纯的任务转发人(分配者)。它根本没有必要持有一个Handler的列表再分别逐个调用,还要管理该继续还是该中断等等逻辑(这些逻辑是依赖每个Handler返回的标志来决定的)。何不让Handler自己负责把控制向后分发,主控类只需要知道领头的那个Handler最终会把事情处理好就行了。这种结构还有一个好处,就是每个Handler可以自行决定是否该往下传递控制,还可以根据需要替换上下文实例的实现来影响后续的处理。(这一步与上一步是二选一,有些人喜欢在主控类中持有Handler队列,有些人喜欢链式Handler。我个人认为问题不大,两者的实现难度也没有差别,实现需求就行)

最后,我们为了交流方便,把这种组合方式称为“责任链”。





大致明白了你的意思。你的意思是,主控类把参数封装为context,然后依次传递到责任链上的handlers。每一个handler都尝试去处理(判断是否符合if条件),如果不能,则委托责任链的下一个handler。按这种方式实现的话,每一个handler都只有一个if/else而已。

妙哉!
9 楼 lazy_ 2013-02-25  
留下的祝福 写道
留下的祝福 写道

这个我知道可以优化的就是用sql关联查出来,可以减少一个if

你的问题,我认为是NULL。你的API控制好,不要返回NULL,能提高不少效率。
8 楼 留下的祝福 2013-02-25  
留下的祝福 写道

这个我知道可以优化的就是用sql关联查出来,可以减少一个if
6 楼 lazy_ 2013-02-25  
lazy_ 写道
我觉得,在请求发起方,就应该封装一下请求,作为一个策略对象传到服务处理方。服务处理方法直接使用策略去处理,然后返回。但是实际上,请求发起方很可能在构造策略对象的时候就使用了if/else。具体问题具体分析吧。

这种方法实际是把if/else的责任推到了服务使用者,而不是服务提供者,并不能根本上消除,只可能在某些特别的场合消除。
5 楼 留下的祝福 2013-02-25  
正好我碰到,这里如何把那么多if去掉:
List<TWorkFlowwork> wfwList=errorProcessingService.findWorkFlowworkByWorkType("7",workbillcode.getId()+"");
								boolean flag=false;
								if(wfwList!=null&&wfwList.size()>0){
									for(int i=0;i<wfwList.size();i++){
										TWorkFlowwork wfw=wfwList.get(i);
										if(wfw!=null){//当前待办是原件校验并且如果已经结束了则激活原件校验待办
											if(wfw.getStatus()!=null&&!"".equals(wfw.getStatus())){
												if("1".equals(wfw.getStatus())){
													flag=true;
													break;
												}
											}
											
										}
									}
									if(!flag){//如果原件校验待办都结束了,则产生一条原件校验待办
										TWorkBillcode wb=errorProcessingService.findWorkBillcodeByParameters(flow.getBussid(),flow.getBusstype());
										wb.setIsmatchpage("1");
										errorProcessingService.updateWorkBillcode(wb);
									}
								}
4 楼 lazy_ 2013-02-25  
我觉得,在请求发起方,就应该封装一下请求,作为一个策略对象传到服务处理方。服务处理方法直接使用策略去处理,然后返回。但是实际上,请求发起方很可能在构造策略对象的时候就使用了if/else。具体问题具体分析吧。

相关推荐

Global site tag (gtag.js) - Google Analytics