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

应用场景

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

……

}

}

优点和缺点

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

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

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

最后补充

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

时间: 2024-12-24 04:10:00

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

在已经安装好的Nginx上增加新模块

下载模块源码本实例以nginx-push-stream-module为例子 [email protected]: cd ~/Downloads/ [email protected]: git clone http://github.com/wandenberg/nginx-push-stream-module.git [email protected]:NGINX_PUSH_STREAM_MODULE_PATH=$PWD/nginx-push-stream-module #设置临时变量用来保存模

如何在Win7电脑上增加新磁盘分区?

原文链接: https://www.cnblogs.com/haoxitong/p/9405497.html 我们在重装好系统Win7系统后有时会碰到需要新建磁盘分区的情况,这时我们再重装系统进行磁盘分区就有些过于麻烦了,其实我们可以利用Win7系统自身的磁盘管理功能来新建一个磁盘分区.下面好系统重装助手就来介绍一下好系统Win7系统电脑磁盘新建分区的方法. 好系统Win7系统电脑磁盘新建分区的方法 1.右键点击我的电脑,选择管理,在计算机管理界面点击左侧栏中的“磁盘管理”,选择一个可以压缩空间

旧服务器上源代码迁移到新服务器

由于旧的vsts源代码服务器即将准备封存,需要将目前在旧服务器上尚在使用的代码全部迁移到新的vsts服务器上. 所以就需要将所有的需要后续使用的代码迁移到新的vsts上面.虽然只是一个代码的迁移工作,但是涉及到的具体细节 还真不少,首先就要列出需要迁移的代码的清单,其次要搞清楚各个代码版本目前的状况,是否有人在编辑,是否近期 有大的发布,或者有项目正在进行中. 首先是将各位目录下都需要迁移的代码在一个清单列表中记录下来,其次就是审核这个清单列表,看是否有遗漏或 者不需要的代码在里面,审核完成后就

给zencart产品增加新字段

经常遇到一些产品具有很丰富的信息,可zencart后台添加产品的时候,就只有那么几个字段.例如产品model,产品库存等等. 想给某个产品定制一个像magento一样的短描述功能,或者想显示该产品在亚马逊上的链接. 这个方法就派上用场了. 我这里讲的就是如何给产品添加一个product_color字段. 1,先去phpmyadmin,找到你网站的数据库,然后找到products表,给该表添加一个product_color字段,不会用sql语句的可手动添加. 2,编辑文件admin/include

C#WinForm 用textbox与button控件,向xml文件中增加新的数据

1 旧的xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <datas> 3 <XianJia> 4 <ShengHao>女娲娘娘</ShengHao> 5 <Password>nwnn</Password> 6 </XianJia> 7 <XianJia> 8 <ShengHao>后土娘娘</Sh

转载:Pixhawk源码笔记十一:增加新的MAVLink消息

转自:新浪长沙@WalkAnt 第十二部分 增加新的MAVLink消息 英文参考:http://dev.ardupilot.com/wiki/code-overview-adding-a-new-mavlink-message/ 本节源自:http://liung.github.io/blog/apm/2014-09-05-APM-增加新的MAVLink通讯协议消息.html MavLink协议:https://pixhawk.ethz.ch/mavlink/ 地面站之间的数据和指令通信都是通过

在AndroidStudio v1.2.0中导入或增加新项目或工程(导入第三方类库或工程)

以下说明基于AndroidStdudio版本v1.2 由于AndroidStudio项目止录与Eclipse中的Worksapce在意义上的改变,所以导入新包或建立新项目时并不和以前那样了. 下面是我碰到的以解决问题的办法.以供参考. 1. 创建好项目后,将需要的第三方jar文件拷贝到工程目录下的app\libs目录中,由于在1.2版本中的build.gradle文件中已经加上了依赖目录,所以不用再改该文件了. dependencies { compile fileTree(dir: 'libs

UML中类图的四种关系及其代码实现

在uml图中 最复杂的也就是泛化,实现,依赖,关联,这四种关系了,如果弄清了这几种关系那么在理解UML图的时候就会变得轻车熟路了! 如果你对着几种关系一点都不熟悉的话可以看一下uml中的四种关系,这篇博客简单的介绍了一下这几种关系,本文将重点的介绍一下,这几种关系在代码里如何实现的! 泛化关系 我想这个也可能是最简单的关系了,泛化就是特殊到一半的过程,也就是继承的相反的过程,子类继承自父类,而父类是从子类泛化而来! 泛化(generalization)关系是一个类(称为子类.子接口)继承另外的一

学习Git的一点心得以及如何把本地修改、删除的代码上传到github中

一:学习Github的资料如下:https://git.oschina.net/progit/ 这是一个学习Git的中文网站,如果诸位能够静下心来阅读,不要求阅读太多,只需要阅读前三章,就可以掌握Git的常用命令. 二:学习Git中远程仓库的问题: Git的远程仓库:就是管理你代码的地方,这个是Github中提供的有,只要你在Github中注册一下你就可以获得无限个Git的远程仓库. 我的Github中的远程仓库如下图所示: 对远程仓库我首先理解为:在Gitbash中敲一个命令:Git init