avatar

目录
记一次代码重构的经历

最近这段时间以来工作上的事情不是很多,就想着找个时间把原来的一块业务逻辑给重构一下,于是就有了这篇记录。

一. 背景

简单的说,当前的业务主要是做个贷相关的比较多。功能上有一个很重要的对外接口,是提供给客户来做额度测算用的。但是呢,客户的来源又不固定,分为很多种不同的渠道。按照以往最开始的做法,先是有了这么一个测额的业务需求,那么好,就给它加上这么一个新接口。接着,下次又来了一个需求,是另外的渠道的用户也想来测额,那么这个时候往往是两种做法:

  1. 再加一个新接口以实现不同的业务逻辑;

  2. 沿用原来老的接口,无非是多加几个条件判断

看似是很轻松地解决了加新需求的问题。但是问题来了,俗话说,没有不变化的需求嘛。万一他下次又来个新需求呢?当然了可以继续偷懒沿用上述的 1 和 2 两种方法,但其实这样做,长此以往肯定是不利于代码维护的嘛。加新接口能解决问题,但是一个接口要真正上线还是要经过申请、审批等等一系列流程的,比较麻烦,费时费力。在老接口里继续加判断条件,那最终肯定就是会出现诸如这样的代码:

java
1
2
3
4
5
6
7
8
if (condition A is true) {
// bla bla ...
} else if (condition B is true) {
// bla bla ...
} else if (condition C is true) {
// bla bla ...
}
...

是不是开始嗅到代码的 “坏味道” 了?要是这么任由其长年累月的发展下去,那保不齐那天就会被人指着说,“看看这屎山一般的代码”!所以啊,为了不给后人留下闲话,咱能完善的就给它完善了吧!

二. 重构

当时的现有代码确实已经是有一堆的 if 了哈,碰巧呢,组长又让我接了一个新需求,好家伙,那不是还要继续 if? 我心想,别吧!是时候捣鼓捣鼓设计模式了。但是经典的设计模式不是多达二十好几种吗,用哪一种呢?结合业务特点,由于客户来自于四面八方的渠道,所以它们对应到后端各自的授信模型和数据结构也就有所不同,相当于说,在这里,每个渠道可以看成是一种策略,那这不正好可以用上策略模式了吗?先来温习一下策略模式的套路代码:

  1. 首先,得定义一个策略接口,OOP 的精髓嘛,面向抽象编程,其中有一个主方法用于实现业务逻辑
  2. 针对具体的策略,再定义出来策略接口的不同实现类,覆盖接口方法实现自己的业务逻辑
  3. 最后有一个 Context 类,相当于一个客户端暴露给用户使用,其中包含策略接口的对象引用,用户调用这个类时传递不同的接口实现,以完成具体的业务计算

结合业务用代码描述大概便是:

  1. 策略接口
java
1
2
3
public interface LimitStrategy {
Result getLimitResult();
}
  1. 不同的子类实现
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component // 结合Spring使用,交给 ApplicationContext 管理
public class ConcreteLimitStrategyA implements LimitStrategy {

@Override
public Result getLimitResult() {
Result result = new Result();
// 实现A自己的业务逻辑
...
return result;
}
}

@Component
public class ConcreteLimitStrategyB implements LimitStrategy {

@Override
public Result getLimitResult() {
Result result = new Result();
// 实现B自己的业务逻辑
...
return result;
}
}
  1. Context 类
java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class LimitContext {

private LimitStrategy strategy;

public LimitContext(LimitStrategy strategy) {
this.strategy = strategy;
}

// 客户端统一调用这个方法
public Result getLimitResult() {
return strategy.getLimitResult();
}
}

可以看到,具体得出什么结果来,依赖于 LimitStrategy 接口的具体实现,那这个实现类要怎么获取呢?这里我采用了简单工厂模式结合不同的业务入参来生成具体的策略实现类。当然了,你可能会说,简单工厂模式有很多弊端,比如把所有的类实例化代码逻辑放到了一块儿, 违反单一职责原则等等,但是我觉得本次重构的重点在于使用策略类实现不同的业务逻辑代码,这里的改造反而相对是比较次要一些的,用简单工厂模式也无妨。而且这样一来,还可以避免对外暴露具体的策略实现类,代码如下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SimpleStrategyFactory {

// BizParam 类封装了用于判断渠道的业务入参
public static LimitStrategy createStrategy(BizParam params) {
if ("channelA".equals(ChannelHelper.getChannel(params))) {
return SpringContextUtils.getBean(Constant.CHANNEL_A);
} else if ("channelB".equals(ChannelHelper.getChannel(params))) {
return SpringContextUtils.getBean(Constant.CHANNEL_B);
}
// 具体返回 null 还是有默认策略实现类兜底可结合业务再斟酌
return null;
}
}

public class Constant {

public static final String CHANNEL_A = "此处填写 A 策略实现类在 Spring 容器中的唯一标识"

public static final String CHANNEL_B = "此处填写 B 策略实现类在 Spring 容器中的唯一标识"
}

这样一顿改造之后,原来一堆的 ifelse的地方就可以改成

java
1
2
3
4
5
6
7
8
9
public class Client {
// 对外提供的接口
public Result getLimitResult(Request request) {
BizParam params = getBizParam(request);
LimitStrategy strategy = SimpleStrategyFactory.createStrategy(params);
LimitContext context = new LimitContext(strategy);
return context.getLimitResult();
}
}

这样一来,在真正方法调用的地方就再也见不到那些 “讨厌” 的 ifelse if 了,相比起未重构之前的:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client {
// 对外提供的接口
public Result getLimitResult(Request request) {
BizParam params = getBizParam(request);
Result result = new Result();
if ("channelA".equals(ChannelHelper.getChannel(params))) {
// 几十行业务代码
result = ...;
} else if ("channelB".equals(ChannelHelper.getChannel(params))) {
// 几十行业务代码
result = ...;
} else if ("channelC".equals(ChannelHelper.getChannel(params))) {
// 几十行业务代码
result = ...;
}

return result;
}
}

是不是感觉一下子也清爽了很多呢?

下面总结一下策略模式在使用上的一些优劣之处。

好处:

  • 显而易见地,可以去掉大量繁冗的代码,使代码结构更加清晰易读,可维护
  • 具体的策略实现类可以随时替换,而不用去改调用处的代码。可以很方便的适应需求变更,做到拥抱变化

坏处:

  • 随着不同策略的增多,其实现类也会随之增多,可能带来 “类爆炸” 这一现象
文章作者: JanGin
文章链接: http://jangin.github.io/2020/08/02/a-code-refactoring-experience/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JanGin's BLOG

评论