您的位置:首页 > 编程语言

一种在旧代码上增加新需求的重构模式

2016-12-05 23:06 309 查看

应用场景

相信大家遇到过这种场景:
旧代码中已经有一堆的if-else或者switch-case了;产品却要求在这段流程里增加一个新的功能。
这种时候大家会怎么做?
我的建议是:
重构这段代码。在重构的基础上,加入新的功能。
肯定会有人说:
工期本来紧张,再对原有代码进行重构,岂不会更加捉襟见肘?
这里介绍的(也是我在实践中经常使用的)这种方式,我称之为“接口-分发器模式”。它可以在尽量减少重构工作量的同时,完成大部分重构工作。

类图

接口-分发器类图

接口

这个模式首先将旧代码/功能抽取为一个接口(ServiceInterface.java)。这个接口的抽象能力,应该能够同时覆盖旧代码中的原有逻辑和新需求中的功能。换句话说就是新、旧代码都可以抽象为同一个接口。
如果这一点都无法做到,建议先回头想想这两段逻辑应不应该放到同一个抽象内。

例如我在一次重构中所做抽取的接口:

public interface RequestApprover { void approveById(Integer id, Request requestInfo, UserInfo approver) throws ServiceException;}
这个接口是对请求数据(Request)的审批操作的抽象。
请求数据一共有三类(其中旧类型两种,新需求一种);审批操作同样也有三类(同样,旧类型两种,新需求一种)。这样,最多会有九种审批逻辑(不过实际中只有六种)。而这些审批逻辑和代码,都可以用这一个接口来描述。

分发器

分发器(ServiceDispatcher.java)是服务的入口。但它本身并不提供任何业务服务,而只负责将请求分发给实际的服务处理类。
从这一点上看,分发器其实很像一个工厂。这么说也没错,不过这个分发器的重点在于“分发”,而不是“创建”。
另外,将它隐藏在对外接口之下,是因为我将这个分发器理解为接口的一种实现;它属于抽象之中,不需要被抽象之外的调用者感知。这是我个人偏好。
对应前面的接口,我用到的分发器是这样的。
class RequestApproverAsDispatcher RequestApprover { private RequestApprover approver4First4NotLate; private RequestApprover approver4First4PseudoOver; private RequestApprover approver4Final4NotLate; private RequestApprover approver4Final4M1; private RequestApprover approver4Final4PseudoOver; @Override public void approveById(Integer id, Request requestInfo, UserInfo approver) throws ServiceException { RequestApprover requestApprover; switch (some_field) { case FIRST_APPROVED: case FIRST_REJECTED: requestApprover = xxx; break; case APPROVED: case REJECTED: requestApprover = yyy; break; default: throw new UnsupportedOperationException(); } requestApprover.approveById(id, requestInfo, approver); }}

具体服务类

具体服务类承担实际上的业务逻辑。在类图中,它们被表示成了Service4Scene1.java ~ Service4Scene7.java。并且,我专门画了ServiceAsAdapter.java和ServiceAsSkeleton.java 来表示:这些具体服务类还可以有自己的组织方式、应用自己应用的模式。
在我上面的例子中,我通过一个RequestApproverAsSkeleton.java定义了模板。而在另一项需求中,我用了组合和中介——至少我将那几个类理解为中介模式。

小结

本质上,这个所谓“接口-分发器模式”是一种策略模式。但是它比策略模式多一点东西——分发器。另外,在实践应用中,它不可能只有策略。在“具体服务类”的组织上,几乎都会用上更多的模式。
题外话,就设计模式的应用上,有策略则必有工厂,有工厂几乎必有单例,这似乎也自成一种“模式”。

重构

那么,这个“模式”要怎样应用到重构中呢?
很简单——让旧代码和新代码都成为“具体服务类”中的成员,并且是不同的成员。
仍以上面的例子来说,我将旧代码和新代码分别安排在这两个类中。再结合前面的分发器,很简单的就完成了这次重构,并同时完成了新需求。
旧代码在这个类中:
class RequestApprover4First extends RequestApproverAsSkeleton { private static final Logger LOGGER = LoggerFactory .getLogger(RequestApprover4First.class); private RequestService service; @Override protected void approve(Request requestInfo, Request request) throws InvalidDataException { …… } @Override protected void reject(Request requestInfo, Request request) { // 不做处理 } @Override protected void configRequest(Request requestInfo, Request request, UserInfo approver) { …… } }
而新的业务在这个服务中:
class RequestApprover4Check extends RequestApproverAsSkeleton { private static final Logger LOGGER = LoggerFactory .getLogger(RequestApprover4Check.class); @Override public void approveById(Integer id, Request requestInfo, UserInfo approver) throws ServiceException { …… // 这个方法中有额外处理 } @Override protected void approve(Request requestInfo, Request request) throws ServiceException { …… } @Override protected void reject(Request requestInfo, Request request) { // 不做任何操作 } @Override protected void configRequest(Request requestInfo, Request request, UserInfo approver) { …… } }

优点和缺点

优点应该说比较明显:新、旧逻辑和代码被隔离开了,也就完成了解耦合。并且后续如果还要加新的需求,也可以比较轻松的隔离到新的服务类中。相信接手过旧系统、旧代码的朋友们都能理解其中的意义。
另外,旧代码可以保持不动,或者简单的复制到对应的具体服务类中。因此,改造工作量比较小。

缺点呢?一是容易造成“类爆炸”。虽然不一定变得太多,但是类的数量肯定比不用模式要多。二是这种模式有时候不会(也不需要)对旧代码做任何改动。这样一来,重构目标实际上并没有实现。

最后补充

做重构之前,一定要有用于验证旧代码功能的测试,并且尽可能的覆盖流程分支。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  模式 分发 重构