应用场景

相信大家遇到过这种场景:
旧代码中已经有一堆的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) {
            ……
    }
 
}

 

优点和缺点

优点应该说比较明显:新、旧逻辑和代码被隔离开了,也就完成了解耦合。并且后续如果还要加新的需求,也可以比较轻松的隔离到新的服务类中。相信接手过旧系统、旧代码的朋友们都能理解其中的意义。

另外,旧代码可以保持不动,或者简单的复制到对应的具体服务类中。因此,改造工作量比较小。


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

最后补充

做重构之前,一定要有用于验证旧代码功能的测试,并且尽可能的覆盖流程分支。