闲话COR
?? 说起COR(责任链模式),大家最熟悉可能就是阎老在他那部大部头《设计模式》中举的‘击鼓传花’的形象例子,那里实现了一个很传统的COR模式。而从我看过的COR实现的方式上讲,从业务角度上区分,可分为两种:
完全推卸责任的COR部分推卸责任的COR?? 当我们在开发一个项目或者产品时,为了职责分离以及开发的效率,我们常常采用的是分层分模块开发,一部分人负责前台,一部分人负责业务层,一部分人负责数据访问层等等。当开发完成后,我们都会移交给QA进行测试。当QA测试发现问题时,就会去找前台的人(大多数情况下,QA一般不知道这个问题是归谁负责,所以一般都从源头找起)。
完全推卸责任的场景:前台的人一看QA报的问题,发现不是属于前台的范畴,于是就把BUG转给了中间层的开发(业务层),说你帮忙看看,你们传过来的数据不对。中间层的开发DEBUG了下,发现从数据访问层来的数据就已经出错了,于是又把BUG转给了数据访问层的人员,数据访问层的人员查了查,发现问题是他们自己的数据问题,于是便把BUG Fix了,移交QA验证。部分推卸责任的场景:而对于部分推卸责任的场景来说,就是前台,中间层,数据访问层都存在问题,每一层都需要进行代码的修改,才能够将这个BUG Fix。?? 对于上面的场景来说,QA相当于COR模式中的调用者(CLIENT端),我不管你们谁负责处理这个问题,我只负责发现问题然后移交给你们。而对于前台,中间层和数据访问层的开发人员来说,相当于COR模式中的处理者。处理者不管问题从哪里来,他只看下是不是属于他的问题,如果是那他就FIX然后返回,如果不是,那就传给下一个处理者处理。
?
?? 当我们了解了这两种场景后,我们来看看具体的实现,首先先看下传统的COR模式。
?
在每个具体的IHandler实现类中,都会先CHECK,看下BUG是不是属于自己的处理范畴,如果是则处理,否则,传给下一个IHandler处理。
?
?我们有了一个基类AbstractHandler来处理所有IHandler的共同逻辑-转发bug给下一个IHandler,如果该IHandler自己不能处理的话.?
可以看到,IHandler#handle()方法的返回类型由boolean变回了void,但是增加了一个checkIfIcanHandle(bug)方法.基类AbstractHandler也需要改变。
??? 而且我们很容易看出这里面其中的变化点,比如说,付款,我们可以通过网上银行付款,也可以通过支付宝来付款等等,那么对于这样的变化,我们怎么应对?很自然的方式就是继承这个BuyGoods类,然后覆写其中的付款方法,pay4good(),像评价卖家,我们可以有好评,中评,差评以及各种各样的评价,那么我们也可以对不同的评价行为分别为其覆写其中的evaluate(),看出来了吗?这里用了很常见的Template模式。如果需求到这里为此,我觉得还是可以接受。但是,如果我要买的东西很贵,那么我挑选完商品,我就不能简单的直接下订单了,我得跟卖家好好砍砍价后才能下订单。如果沿用我们上面的模型,那我们得在下订单前加入一个步骤,协商价格:
??? 这样一来,由于基类BuyGoods的步骤发生了变化,导致其所有的子类都需要增加negotiation()方法的实现,即使它不需要进行协商价格(这种情况下的子类实现会为该方法放置一个空实现)。再或者,我们在拿到货物后发现有损坏了,要退货,那么这个时候又要修改BuyGoods基类增加接口。当然,对于这种变化的需求,我们完全可以通过增加协商价格接口和退货接口,并让新的BuyGoods子类来实现,也可以通过Adapator模式来引入新的方法,如果这个买商品的流程只有少许这样的变化,那么我认为增加接口或者Adapator来引入新方法的方式是可以满足的。如果这样的变化很多,那么我们能引入多少接口,这个时候使用COR或许是一种更好的选择。而且,还有可能有这样的变化,就是步骤的顺序不固定,可能我在A店铺这里是协商好价格再下订单,在B店铺那里则可能是下了订单后再协商的价格。所以,当一个流程(或算法),它的步骤不确定而且步骤的顺序有可能变化时,那么COR就能派上用场。???? 如果采用COR,那么上面的BuyGoods的每个方法都演化成一个IHandler实例,这些IHandler实例按照顺序组合成一个IHandler chain,买商品实际上就变成了调用这个IHandler chain。
?????
?
???? 当我们采用COR后再看看前面的需求变化,第一种需求变化是步骤的增加,比如说增加协商价格/退货,这个时候只需要增加对应的IHandler实例,并注册到IHandler chain中即可,不需要修改别的IHandler实例或者client代码,完全符合开闭原则。对于第二种需求变化,那就更简单了,只需要修改注册到IHandler chain的IHandler实例顺序即可(大多数情况下,我们注册IHandler到IHandler chain都是通过配置文件的配置来实现的),这里的IHandler chain实际上就是上述的AbstractHandler。?
????? 除了上面的应用场景以外,实际上使用Command的地方也可以使用COR,举个最常见的Command例子,就是我们常常在处理WEB请求的时候,规定某一类请求对应于某一种处理,比如说,如果发过来的HTTP REQUEST的请求TYPE为List,那么就调用ListAction进行处理这个请求,如果请求TYPE为Save,那么就调用SaveAction进行处理。而在主程序启动的时候就已经规定好了这样的映射,通常是请求Type作为key,对应处理请求的action作为value储存在一个map中,当有请求过来的时候,那么就根据请求的type从该map中取出action进行处理。这个很明显是一个典型的Command模式,但是,当请求与对应的处理请求的action的对应关系并不是这么简单的时候,比如,要采用什么action来处理这个请求,需要满足的条件是可变的,易变的,即不仅仅是根据请求的type,而还需要根据请求里面的用户信息,请求的某些hearder信息来一起确定时,那么cor会比command要更加适用。