面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判定。假如你还在大量使用if else,当然,界面表现层除外,即使你使用java/C#这样完全面向对象的语言,也只能说明你的思维停留在传统的面向过程语言上。
传统思维习惯分析
为什么会业务逻辑层使用if else,其实使用者的目的也是为了重用,但是这是面向过程编程的重用,程序员只看到代码重用,因为他看到if else几种情况下大部分代码都是重复的,只有个别不同,因此使用if else可以避免重复代码,并且认为这是模板Template模式。
他范的错误是:程序员只从代码运行顺序这个方向来看待它的代码,这种思维类似水管或串行电路,水沿着水管流动(代码运行次序),当碰到几个分管(子管),就分到这几个分管子在流动,这里就相当于碰到代码的if else处了。
而使用OO,则首先打破这个代码由上向下顺序等同于运行时的先后循序这个规律,代码结构不由执行循序决定,由什么决定呢?由OO设计;设计模式会取代这些if else,但是最后总是由一个Service等总类按照运行顺序组装这些OO模块,只有一处,这处可包含事务,一般就是Service,EJB中是session bean。
一旦需求变化,我们更多的可能是Service中各个OO模块,甚至是只改动Service中的OO模块执行顺序就能符合需求。
这里我们也看到OO分离的思路,将以前过程语言的一个Main函数彻底分解,将运行顺序与代码其他逻辑分离开来,而不是象面向过程那样混乱在一起。所以有人感慨,OO也是要顺序的,这是肯定的,要害是运行顺序要单独分离出来。
是否有if else可以看出你有没有将运行顺序分离到家。
设计模式的切入口
经常有人反映,设计模式是不错,但是我很难用到,其实假如你使用if else来写代码时(除显示控制以外),就是在写业务逻辑,只不过使用简单的判定语句来作为现实情况的替代者。
还是以大家熟悉的论坛帖子为例子,如ForumMessage是一个模型,但是实际中帖子分两种性质:主题贴(第一个根贴)和回帖(回以前帖子的帖子),这里有一个朴素的解决方案:
建立一个ForumMessage,然后在ForumMessage加入isTopic这样判定语句,注重,你这里一个简单属性的判定引入,可能导致你的程序其他地方到处存在if else 的判定。
假如我们改用另外一种分析实现思路,以对象化概念看待,实际中有主题贴和回帖,就是两种对象,但是这两种对象大部分是一致的,因此,我将ForumMessage设为表达主题贴;然后创建一个继续ForumMessage的子类ForumMessageReply作为回帖,这样,我在程序地方,如Service中,我已经确定这个Model是回帖了,我就直接下溯为ForumMessageReply即可,这个有点类似向Collection放入对象和取出时的强制类型转换。通过这个手段我消灭了以后程序中if else的判定语句出现可能。
从这里体现了,假如分析方向错误,也会导致误用模式。
讨论设计模式举例,不能没有业务上下文场景的案例,否则无法决定是否该用模式,下面举两个对比的例子:
第一. 这个帖子中举例的第一个代码案例是没有上下文的,文中只说明有一段代码:
main() {
if(case A){
//do with strategy A
}else(case B){
//do with strategy B
}else(case C){
//do with strategy C
}
}
这段代码只是纯粹的代码,没有业务功能,所以,在这种情况下,我们就很难确定使用什么模式,就是一定用策略模式等,也逃不过还是使用if else的命运,设计模式不是魔法,不能将一段毫无意义的代码变得简单了,只能将其体现的业务功能更加轻易可拓展了。
第二.在这个帖子中,作者举了一个PacketParser业务案例,这段代码是体现业务功能的,是一个数据包的分析,作者也比较了各种模式使用的不同,所以我们还是使用动态代理模式或Command模式来消灭那些可能存在的if else
由以上两个案例表明:业务逻辑是我们使用设计模式的切入点,而在分解业务逻辑时,我们习惯则可能使用if else来实现,当你有这种企图或者已经实现代码了,那么就应该考虑是否需要重构Refactoring了。
if else替代者
那么实战中,哪些设计模式可以替代if else呢?其实GoF设计模式都可以用来替代if else,我们分别描述如下:
状态模式
当数据对象存在各种可能性的状态,而且这种状态将会影响到不同业务结果时,那么我们就应该考虑是否使用状态模式,当然,使用状态模式之前,你必须首先有内存状态这个概念,而不是数据库概念,因为在传统的面向过程的/面向数据库的系统中,你很难发现状态的,从数据库中读取某个值,然后根据这个值进行代码运行分流,这是很多初学者常干的事情。参考文章:状态对象:数据库的替代者
使用传统语言思维的情况还有:使用一个类整数变量标识状态:
public class Order{
PRivate int status;
//说明:
//status=1 表示订货但为查看 ;
//status=2 表示已经查看未处理;
//status=3 表示已经处理未付款
//status=4 表示已经付款未发货
//status=5 表示已经发货
}
上述类设计,无疑是将类作为传统语言的函数来使用,这样导致程序代码中存在大量的if else。
策略模式
当你面临几种算法或者公式选择时,可以考虑策略模式,传统过程语言情况是:从数据库中读取算法数值,数值1表示策略1,例如保存到数据库;数值为2表示策略2,例如保存到xml文件中。这里使用if else作为策略选择的开关。
command模式
传统过程的思维情况是:假如客户端发出代号是1或"A",那么我调用A.java这个对象来处理;假如代号是2或"B",我就调用B.java来处理,通过if else来判定客户端发送过来的代码,然后按事先约定的对应表,调用相应的类来处理。
MVC模式
MVC模式的传统语言误用和Command模式类似,在一个Action类中,使用if else进行前后台调度,假如客户端传送什么命令;我就调用后台什么结果;假如后台处理什么结构,再决定推什么页面,不过,现在我们使用Struts/JSF这样MVC模式的框架实现者就不必范这种低级错误。
职责链模式
职责链模式和Command模式是可选的,假如你实在不知道客户端会发出什么代号;也没有一个事先定义好的对照表,那么你只能编写一个个类去碰运气一样打开这个包看一下就可以。与Command是不同在AOP vs Decorator一文中有分析。
代理或动态代理模式
代理对象可以是符合某种条件的代表者,比如,权限检验,传统面向过程思维是:当一个用户登陆后,访问某资源时,使用if else进行判定,只有某种条件符合时,才能答应访问,这样权限判定和业务数据逻辑混乱在一起,使用代理模式可以清楚分离,假如嫌不太好,使用动态代理,或者下面AOP等方式。
AOP或Decorator模式
其实使用filter过滤器也可以替代我们业务中的if else,过滤器起到一种过滤和筛选作用,将符合本过滤器条件的对象拦截下来做某件事情,这就是一个过滤器的功能,多个过滤器组合在一起实际就是if else的组合。
所以,假如你实在想不出什么办法,可以使用过滤器,将过滤器看成防火墙就比较好理解,当客户端有一个请求时,经过不同性质的防火墙,这个防火墙是拦截端口的;那个防火墙是安全检查拦截等等。过滤器也如同红蓝白各种光滤镜;红色滤镜只能将通过光线中的红色拦截了;蓝色滤镜将光线中的蓝色拦截下来,这实际上是对光线使用if else进行分解。
如图,通过一个个条件过滤器我们立体地实现了对信号的分离,假如你使用if else,说明你是将图中的条件1/2/3/4合并在一起,在同一个地方实现条件判定。
需要深入了解过滤器的实现细节和微小区别,请参考文章:AOP vs Decorator