“设计模式”这个词想必各位大佬肯定不陌生。他最早诞生于1994年,由著名的四人帮(GOF):Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides首次提出。目的在于增加代码的可重用性、可读性,可扩展性和可靠性,让代码呈现“高内聚、低耦合”的特点。相信非常多的人认认真真学完23种设计模式之后,一定有这种感觉 —— 好像懂了,但好像又没懂,我为啥学来着,学这玩意儿有啥用来着?然后捧着书双眼发直陷入迷茫。。。。。。。
别慌兄dei,你不是一个人。当时我听人劝,吭哧吭哧啃完了设计模式这本书的时候都想骂娘。这玩意儿咋用,从来没在业务代码里见过啊。你跟我说框架里有,那是,框架变化程度多小啊,业务代码这家伙成天变,今天这样,老板想法一变就得是另一个样,你敢用设计模式?分分钟累死你。
直到一年前的那一天。那是一个风和日丽的下午,我怀着忐忑的心情接手了俺们的资金结算系统,上一波人给我们简单讲了讲就开始安排我们看代码,然后我就看到了我职业生涯见过的最大的一个方法:800行
(过于暴力,无法展示)
我根本就捋不清这个方法前后都是咋依赖的,一堆if else都在干啥,恶心的我想砸键盘。后来想想,算了,我自己花钱买的。。。。感恩前辈,治好了我的代码强迫症。。。。
再到后来,我实在是看不下去了,就开始琢磨咋能让代码清楚一点儿,这个时候,我想到了设计模式。(重点来了)
业务背景
这个800行的方法的业务场景是这样式儿的:外部系统调用交易系统想要对外付款,但是外部系统实际上并不知道付款的具体信息,只知道我要付给谁,付多少钱。因此我们作为结算系统,需要替业务方补充非常多的信息,例如:付款账户,收款账户,付款主体,收款主体,收款商户,支付银行,收款银行......而这些信息在补充的时候,由于各个上游系统能够提供的信息不一样,因此需要根据提供信息的不同调用不同的第三方接口获取需要补充的信息。同时,补充某条信息的时候,还可能依赖上一条信息的结果,举个例子:
当然实际业务上非常复杂,涉及到的相互依赖比较多,数据的上下游关系复杂,图省事的话放到一个方法里最简单。数据都在一个方法里,不用考虑拆分之后的入参出参都是啥。但是显然这种图省事会给维护人带来非常大的困扰,我们需要拆分这个800行的屎山。
思考
花时间梳理清楚逻辑关系后,发现大部分参数在填充的时候并不相互依赖。那么我们处理这种业务场景的时候,哪种方式最合适?我的第一个反应是,这个场景和Filter好像啊,每个filter只处理自己关注的部分,自己处理不了就交个下个filter处理。再深入思考一下,好像没毛病啊,我们搞个接口,让填充参数的各个handler都实现这个接口,然后在service层获取所有实现该接口的handler循环调用一下就可以了。
最后想一想,这玩意儿是个啥?跟过滤器类似,责任链模式。
上代码
如果完全实现上面所说的功能,代码量太大了,咱给简化一下,我们就补充付款账户、付款主体、付款银行、商户:
先定义个付款单实体:
@Data
public class Payment {
private String accountNo;
private String accountName;
private String merchantCode;
private String leCode;
private String bank;
}
再定义个接口:
public interface DataHandler {
boolean isOutBuilder();
void buildPayment(Payment payment);
}
注意这个isOutBuilder用来标记,是不是最外层的填充。例如上面的图里面,商户和付款账户的isOutBuilder就是true;付款银行和付款主体因为依赖付款账户,所以isOutBuilder就是false。
进一步,由于每个DataHandler的实现都有可能有依赖他的下级handler(例如付款银行和付款主体)。因此定义一个模板抽象类(模板模式????哈哈):
public abstract class AbstractPaymentHandler {
// 调用下级handler
protected void doNextNode(Payment payment) {
// BeanUtils是一个工具类,他实现了ApplicationContextAware,可以根据类型,名字,接口获取bean
Map<String, DataHandler> beansImplementDataHandler = BeanUtils.getBeanOfType(DataHandler.class);
List<String> names = getNextNodeName();
if (names == null || names.size() <= 0) {
return;
}
for (String nextNodName : names) {
DataHandler dataHandler = beansImplementDataHandler.get(nextNodName);
if (dataHandler != null) {
dataHandler.buildPayment(payment);
}
}
}
// 由子类实现,返回这个handler的下级handler的beanName有哪些
protected abstract List<String> getNextNodeName();
}
下面就是各个拼装参数的具体实现类:
拼接账户
@Service
public class FillAccountHandler extends AbstractPaymentHandler implements DataHandler {
@Override
public boolean isOutBuilder() {
return true;
}
@Override
public void buildPayment(Payment payment) {
payment.setAccountName("账户名称");
payment.setAccountNo("账户编号");
// 调用父类方法,执行下级handler
doNextNode(payment);
}
@Override
protected List<String> getNextNodeName() {
// 银行和付款主体依赖账户填充
return Lists.newArrayList("fillBankHandler", "leHandler");
}
}
拼接银行
@Service
public class FillBankHandler extends AbstractPaymentHandler implements DataHandler {
@Override
public boolean isOutBuilder() {
return false;
}
@Override
public void buildPayment(Payment payment) {
payment.setBank(payment.getAccountName() + "的银行");
}
@Override
protected List<String> getNextNodeName() {
return null;
}
}
拼接主体
@Service
public class LeHandler extends AbstractPaymentHandler implements DataHandler {
@Override
protected List<String> getNextNodeName() {
return null;
}
@Override
public boolean isOutBuilder() {
return true;
}
@Override
public void buildPayment(Payment payment) {
payment.setLeCode(payment.getAccountName() + "主体编码");
}
}
拼接商户号
@Service
public class MerchantHandler extends AbstractPaymentHandler implements DataHandler {
@Override
protected List<String> getNextNodeName() {
return null;
}
@Override
public boolean isOutBuilder() {
return false;
}
@Override
public void buildPayment(Payment payment) {
payment.setMerchantCode("商户号");
}
}
最后,在800行代码处,代码变成了这个样子:
@Service
public class PaymentFillService {
public void fillPayment(Payment payment) {
// BeanUtils是一个工具类,他实现了ApplicationContextAware,可以根据类型,名字,接口获取bean
Map<String, DataHandler> handlers = BeanUtils.getBeanOfType(DataHandler.class);
List<DataHandler> handler = handlers.values().stream().filter(DataHandler::isOutBuilder).collect(Collectors.toList());
for (DataHandler dataHandler : handler) {
dataHandler.buildPayment(payment);
}
}
}
可以看见,这个方法里清楚多了,不管你要拼装多少参数,这个方法里都这样,不用变。
总结总结
现在我们回过头来,总结总结整个改造的得失
好处:
- 模块拆分的很清晰,各自是干啥的很清楚
- 代码量总体上没有减少,但是入口处减少很多
- 后续如果要增加新模块,直接实现接口继承抽象类即可,不需要改动原始代码
坏处:
- 整体上代码的复杂度是有一定上升的,没接触过这种写法的新人可能看起来会比较费劲
- 如果依赖关系发生变化,可能需要重构依赖逻辑,这块儿是个未知数
最后