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

简介:

应用场景

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

 

优点和缺点

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

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


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

最后补充

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




本文转自 斯然在天边 51CTO博客,原文链接:http://blog.51cto.com/winters1224/1879788,如需转载请自行联系原作者
相关文章
|
1月前
|
存储 网络协议 搜索推荐
宏函数的代码替换机制会对程序的可移植性产生什么影响
宏函数的代码替换机制可能导致程序可移植性降低,因为它在预处理阶段直接替换文本,可能引发类型不匹配、副作用等问题,不同编译器和平台表现不一。
|
4月前
|
缓存 前端开发 数据格式
构建前端防腐策略问题之保证组件层的代码不受到接口版本变化的问题如何解决
构建前端防腐策略问题之保证组件层的代码不受到接口版本变化的问题如何解决
|
4月前
|
程序员 C++ 开发者
C++命名空间揭秘:一招解决全局冲突,让你的代码模块化战斗值飙升!
【8月更文挑战第22天】在C++中,命名空间是解决命名冲突的关键机制,它帮助开发者组织代码并提升可维护性。本文通过一个图形库开发案例,展示了如何利用命名空间避免圆形和矩形类间的命名冲突。通过定义和实现这些类,并在主函数中使用命名空间创建对象及调用方法,我们不仅解决了冲突问题,还提高了代码的模块化程度和组织结构。这为实际项目开发提供了宝贵的参考经验。
69 2
|
5月前
软件复用问题之衡量是否应该复制或复用代码,如何解决
软件复用问题之衡量是否应该复制或复用代码,如何解决
|
5月前
|
JSON 前端开发 Java
代码的应用重构问题之BaseActivity类的主要功能问题如何解决代码缩减的主要问题如何解决
代码的应用重构问题之BaseActivity类的主要功能问题如何解决代码缩减的主要问题如何解决
|
算法 开发工具 git
多主复制下处理写冲突(3)-收敛至一致的状态及自定义冲突解决逻辑
主从复制模型的数据更新符合顺序性原则:若同一字段有多个更新,则最后一个写操作决定该字段的终值。
140 0
|
JavaScript 前端开发
前端案例:我的备忘录(支持事件的增加、删除和修改,代码完整)
前端案例:我的备忘录(支持事件的增加、删除和修改,代码完整)
279 0
前端案例:我的备忘录(支持事件的增加、删除和修改,代码完整)
|
算法 人工智能 机器学习/深度学习
写1行代码影响1000000000人,这是个什么项目?
这些万里挑一的年轻技术人不约而同地聚集在了这里。
1625 0
写1行代码影响1000000000人,这是个什么项目?
下一篇
DataWorks