背景
由于我司从事服装行业,所以针对每一项原材料商品sku,基本都会由多家供应商来提供,并且在系统上通过接口进行商品信息对接。
由于前期合作供应商只有几家,所以小伙伴采用if else形式逐家供应商判断后调用接口进行对接。
随着合作供应商逐渐增加,原本if else实现商品对接实现方式,在维护和拓展上无比困难,改造势在必行,对此经过考虑,发现该业务场景使用Java设计模式中的【策略模式】非常适用。
分析
策略模式
官方定义: Strategy模式定义一个算法族,并把每一种可能的算法封装成一个类,这些算法可以在应用程序内部被动态替换。此模式让算法的变法不会影响到使用算法的客户。
要点:
- Strategy模式重在外部对策略(算法)的选择,策略的选择由外部条件决定,也就是说算法的动态的切换;
- Strategy模式中只有具有继承关系的算法类;
- Strategy模式从类的角度,对整个算法加以封装;
以上均来自网络
业务分析
不经过业务场景的技术方案都是瞎扯淡,简单分析一下业务:
- 业务形态上是与供应商对接,实则对接主体是供应商使用的系统;
- 不同供应商存在“使用相同系统(Saas、采购ERP)”、“使用不同系统(自研、采购ERP)”的可能;
- “使用相同系统”时,不同供应商接口路径、交互流程、数据结构一般是一致的;
- “使用不相同系统”时,不同供应商接口路径、交互流程、数据结构可能不一致(取决于如何双方约定);
经过简单业务分析,并结合策略模式的定义和要点,可以得出结论:
- Strategy模式从类的角度,对整个算法加以封装,在实际业务中,我们可以认为,“每一种可能的算法”指的是供应商的使用的系统,不同系统的具体对接方式,即为“算法的具体实现”;
- 策略的选择由外部条件决定,套入到实际业务中,即供应商如何选择对应的系统,需要外部创造条件,具体实现可以为供应商与系统配置映射关系;
- Strategy模式中只有具有继承关系的算法类,实际上部分供应商采用了相同的ERP系统,这批系统原生具备了满足商品信息对接的接口,此时将使用同一个策略类,但当有供应商对该接口进行定制化时,此时继承原有策略进行定制即可。
总的来说,策略模式在商品信息对接的场景上完全适用。
编码设计
对于前面讲过,我们将使用策略模式来实现对接功能,先简单画一下UML
下面先定义一个策略接口
public interface IProductStrategy{
//策略编码
String strategyCode();
//sku获取商品信息
ProductInfo getInfoBySku(String sku);
}
可以看到,以上商品策略接口中,除了定义getInfoBySku(String sku)方法,还定义了strategyCode()方法,结合前面的业务分析,我们可以为每个对接主体系统,定义一个编码,并且使用该编码作为策略编码,如Saas_A_Sys,Saas_B_Sys,Erp_C_Sys等等。
那么此时,Saas系统A的策略实现如下
public class SaasAProductStrategy implements IProductStrategy{
@Override
public String strategyCode(){
return "Saas_A_Sys";
}
@Override
public ProductInfo getInfoBySku(String sku){
ProductInfo info = new ProductInfo();
//按照与Saas系统A的接口交互约定,获取商品信息
//组装ProductInfo对象
//info.setxxx(xxx);
return info;
}
}
供应商ERP系统C的策略实现如下
public class ErpCProductStrategy implements IProductStrategy{
@Override
public String strategyCode(){
return "Erp_C_Sys";
}
@Override
public ProductInfo getInfoBySku(String sku){
ProductInfo info = new ProductInfo();
//按照与供应商ERP系统C的接口交互约定,获取商品信息
//组装ProductInfo对象
//info.setxxx(xxx);
return info;
}
}
策略上下文对象持有所有该策略实现类对象,并且根据不同策略编码选项不同策略,简单实现如下
public class ProductStrategyContext{
//策略与编码映射关系
private Map<String,IProductStrategy> productStrategyMap;
//供应商与策略映射
private ISupplierStrategyCodeProvider supplierStrategyCodeProvider;
public ProductStrategyContext(List<IProductStrategy> strategyList){
//转换
this.productStrategyMap = strategyList.stream()..collect(Collectors.toMap(IProductStrategy::strategyCode, v->v));
}
public ProductInfo getInfoBySku(String strategyCode,String sku){
//根据编码获取策略
IProductStrategy strategy = productStrategyMap.get(strategyCode);
if(strategy == null){
throw new ProductStrategyException("stategy is null");
}
//执行策略
return strategy.getInfoBySku(sku);
}
public ProductInfo getInfoBySku(Integer supplierId,String sku){
//获取供应商的策略编码
String strategyCode = supplierStrategyCodeProvider.getBySupplierId(supplierId);
if(StringUtils.isBlank(strategyCode)){
throw new ProductStrategyException("supplier has not strategyCode");
}
return this.getInfoBySku(strategyCode,sku);
}
}
以上就是基于策略模式实现的商品对接的大概实现,但离实际落地还有差距,且在编码上,也不一定要按照以上代码实现,例如
- 对于策略编码与具体策略的对应关系,可以利用枚举来实现,若基于Spring的话,可以利用Bean的管理机制,由IoC容器来形成映射关系;
- 实际上商品对接接口远不止sku一个参数,肯定会包含其他的业务参数或非业务参数,这就需要对策略接口或实现类进行改造等等;
- 供应商系统的接口地址各不相同,还需将供应商与接口地址进行映射,比如,业务系统中对供应商id与接口地址映射、在网关系统中通过供应商id与接口地址映射等等,考虑到接口地址与业务无强关联,所以我司选择将其剥离业务系统,在网关上做映射。
除此之外,还有需要零碎的问题,在方案落地前需要解决,详细代码我就不贴了(涉密),本文只做技术与业务结合分析。
总结
策略模式其实业务中可运用的范围非常广,除了以上的“商品信息对接”业务场景,还有“商品库存对接”业务场景也同样适用,除此之外,还有很多通用一点的场景,如在前端“菜单页面待办事项统计”场景(菜单上的小红点),完全可以使用策略模式来实现。
设计模式常见于各种框架、类库中,初衷就是为了重用代码、让代码更容易被他人理解、保证代码可靠性。但是在业务代码中,盲目使用设计模式,往往会适得其反,使业务代码可读性与拓展性大大降低。所以想要在业务代码中运用设计模式,一定要根据业务分析得到结论,再考虑是否使用设计模式。
本文仅代表个人观点,欢迎看官指正。
敬畏技术,忠于业务。