稍微有编程常识的人都知道,if-else分支条件是任何编程语言任何业务中必不可少的环节,但是过多的if-else分支让代码变得臃肿不可维护,虽然说switch case某种程度上可以替代if-else让代码变得调理有序,但是没有从根本上解决问题。那么有没有比较好的方式来解决这个问题呢?当然是有,我们不妨先看一个案例:
案例一 自定义退货地址问题
1. 背景描述
会员发起退货退款后,自动化退款逻辑会根据退款信息,所属店铺信息调用自动寻址逻辑,然后把退货地址返回给会员,但是店铺A突然间提了个需求,对于我店铺A的所有退货退款,退货地址我们自己维护一套,你们退款业务从
退款单中识别到是我A店铺的单子后调用我们自己维护的寻址逻辑,然后返回给会员。
2. 解决方案
对于1中的需求,我们不考虑扩展性的情况下很好解决,我在同一退货逻辑中加一个if条件判断,通过店铺A给我的订单标或者店铺id,如果是店铺A的单子我走他们的寻址逻辑,否则走默认的寻址逻辑。用伪代码描述就是:
if(退款单属于店铺A) {
return 店铺A退货地址
} else {
return 通用退货地址
}
3.扩展性
有一天店铺B发现他们的退货地址库没有入菜鸟仓,也需要我们对其退货地址从通用逻辑剥离单独维护,那么我们
的代码逻辑将变成这样:
if(退款单属于店铺A) {
return 店铺A退货地址
} else if(退款单属于店铺B){
return 店铺B退货地址
}
else {
return 通用退货地址
}
那如果后边有店铺C,店铺D,针织100多家店铺都需要实现这种特殊逻辑,我们将如何维护,如果不想出更好的方案,那么代码将变成真正的“面条代码”:
if(退款单属于店铺A) {
return 店铺A退货地址
} else if(退款单属于店铺B){
return 店铺B退货地址
}
···
···
else {
return 通用退货地址
}
案例二 退款类型问题
1.背景描述
在自动化退款流程中,会员发起退款后,逆向交易链路会给我们发送退款消息,自动化退款逻辑会从退款单中识别或者计算出退款类型,并执行相应的逻辑。如果之前只有仅退款类型,那么如果新业务要支持同意退货怎么办?
2.解决方案
对于退款类型新增同意退货,那么可以直接在逻辑中增加if判断支持该类型,并调用相应的逻辑。伪代码:
if(退款类型 是 同意退货) {
同意退货
} else {
仅退款
}
3.扩展性
有一天新需求来了,自动化退款要支持同意退货,那么我们仍可以考虑直接加if-else逻辑,代码逻辑将变成这样:
if(退款类型 是 同意退货) {
同意退货
} else if(退款类型 是 确认收货) {
确认收货
}
else {
仅退款
}
那么后续如果随着业务的发展,需要支持到赔付、换货以及维修等,那么我们将何去何从,继续修改上边的“面条代码”?
if(退款类型 是 同意退货) {
同意退货
} else if(退款类型 是 确认收货) {
确认收货
}
···
···
else {
仅退款
}
显然,这种代码是不可维护的,并且如果交给新人接手,估计内心是崩溃的,改没法改,重构鬼才知道中间有没有什么大坑,整个项目就被这种代码搞得满目疮痍。
解决方案
对于上述两个问题,在讲述解决方案之前先看下图:
从两张图中我们大概可以看出几个关键的点:
1)请求上下文信息中一定要有一个能够抽象出来有辨识度的属性(比如退款类型,或者店铺id)
2)一定要有一个地方存储1)中有辨识度的属性与我们自己定义的执行器的标签的映射关系(配置中心或者DB)
3)要有一个容器存储所有自定义执行器(和spring容器中其他bean隔离),并且能够根据标签获取到响应的执行器
基于以上描述,我们已经有了方案,接下来分别针对退货地址案例给出具体的实现方案。
退货地址接口:
/**
* 寻址接口
*/
public interface ReturnAddressService {
String getAddress(Long sellerId);
String getCode();
}
退货地址接口抽象实现:
/**
* 自定义寻址抽象实现
*/
public abstract class AbstractReturnAddressService implements ReturnAddressService {
@Override
public String getAddress(Long sellerId) {
if(null == sellerId) {
throw new IllegalArgumentException("params is illegal");
}
return execute(sellerId);
}
public abstract String execute(Long sellerId);
}
店铺A自定义寻址实现:
/**
* 店铺A自定义寻址
*/
@Service("storeAReturnAddressService")
public class StoreAReturnAddressServiceImpl extends AbstractReturnAddressService {
@Override
public String execute(Long sellerId) {
return "店铺A退货地址";
}
@Override
public String getCode() {
return StoreReturnAddressEnum.STORE_A.getCode();
}
}
店铺B自定义寻址实现:
/**
* 店铺B自定义寻址
*/
@Service("storeBReturnAddressService")
public class StoreBReturnAddressServiceImpl extends AbstractReturnAddressService {
@Override
public String execute(Long sellerId) {
return "店铺B退货地址";
}
@Override
public String getCode() {
return StoreReturnAddressEnum.STORE_B.getCode();
}
}
自定义寻址执行器容器:
/**
* 自定义寻址容器
*/
@Component
public class CustomReturnAddressHolder implements ApplicationContextAware {
private static Map<String,ReturnAddressService> map = new HashMap<>();
/**
* 初始化容器
*
* @param applicationContext
* @throws BeansException
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(null != applicationContext) {
Map<String,ReturnAddressService> beans = applicationContext.getBeansOfType(ReturnAddressService.class);
if(null != beans && !beans.isEmpty()) {
beans.forEach((k,v) -> {
map.put(v.getCode(),v);
});
}
}
}
/**
* 获取自定义执行器
*
* @param code
* @return
*/
public ReturnAddressService getByCode(String code) {
return map.get(code);
}
}
店铺与执行器code映射关系(暂用本地map代替):
static Map<String,String> map = new HashMap<>();
static {
//店铺与执行器映射关系
map.put(StoreReturnAddressEnum.STORE_A.getCode(),"store_a");
map.put(StoreReturnAddressEnum.STORE_B.getCode(),"store_b");
}
测试验证代码:
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring-root.xml");
CustomReturnAddressHolder returnAddressHolder = context.getBean("customReturnAddressHolder",CustomReturnAddressHolder.class);
String storeA = "store_a";
String storeB = "store_b";
String executorCodeA = null;
String executorCodeB = null;
for(Map.Entry<String,String> entry : map.entrySet()) {
if(entry.getValue().equals(storeA)) {
executorCodeA = entry.getKey();
continue;
}
if(entry.getValue().equals(storeB)) {
executorCodeB = entry.getKey();
}
}
ReturnAddressService storeAService = returnAddressHolder.getByCode(executorCodeA);
ReturnAddressService storeBService = returnAddressHolder.getByCode(executorCodeB);
System.out.println(storeAService.getAddress(1L));
System.out.println(storeBService.getAddress(2L));
}
运行测试代码:
根据运行结果,我们看到已经通过利用spring自定义容器加上执行器自带code取巧方案,我们已经实现了消除if-else代码的初衷。并且如果扩展店铺C只需要增加自定义执行器即可,完全不需要修改业务逻辑和容器逻辑,也就实现了执行器和业务逻辑剥离,更高一层也即是业务域和底层平台调用链剥离,从而使我们的业务和代码都变得清晰有致。当然这只是简单的实现了if-else逻辑的消除,阿里内部平台基本都是用了TMF的架构模式,其本质上的原理也是自定义执行器,自定义扩展点,动态加载。至于TMF框架,阿里暂没有开源,想了解的话暂时优点困难。
总结
通过上述一系列描述,我们简单实现了使用业务执行器自定义扩展点的方式去除繁杂的if-else逻辑,让程序变得清晰有条理并且便于扩展,希望在日常开发和学一种对大家有帮助,如果感兴趣可以私聊我,共同探讨共同进步。