设计模式-策略模式在业务中落地

182 阅读6分钟

背景

由于我司从事服装行业,所以针对每一项原材料商品sku,基本都会由多家供应商来提供,并且在系统上通过接口进行商品信息对接。

由于前期合作供应商只有几家,所以小伙伴采用if else形式逐家供应商判断后调用接口进行对接。

随着合作供应商逐渐增加,原本if else实现商品对接实现方式,在维护和拓展上无比困难,改造势在必行,对此经过考虑,发现该业务场景使用Java设计模式中的【策略模式】非常适用。

分析

策略模式

官方定义: Strategy模式定义一个算法族,并把每一种可能的算法封装成一个类,这些算法可以在应用程序内部被动态替换。此模式让算法的变法不会影响到使用算法的客户。

要点:

  • Strategy模式重在外部对策略(算法)的选择,策略的选择由外部条件决定,也就是说算法的动态的切换;
  • Strategy模式中只有具有继承关系的算法类;
  • Strategy模式从类的角度,对整个算法加以封装;

以上均来自网络

业务分析

不经过业务场景的技术方案都是瞎扯淡,简单分析一下业务:

  • 业务形态上是与供应商对接,实则对接主体是供应商使用的系统;
  • 不同供应商存在“使用相同系统(Saas、采购ERP)”、“使用不同系统(自研、采购ERP)”的可能;
  • “使用相同系统”时,不同供应商接口路径、交互流程、数据结构一般是一致的;
  • “使用不相同系统”时,不同供应商接口路径、交互流程、数据结构可能不一致(取决于如何双方约定);

经过简单业务分析,并结合策略模式的定义和要点,可以得出结论:

  1. Strategy模式从类的角度,对整个算法加以封装,在实际业务中,我们可以认为,“每一种可能的算法”指的是供应商的使用的系统,不同系统的具体对接方式,即为“算法的具体实现”;
  2. 策略的选择由外部条件决定,套入到实际业务中,即供应商如何选择对应的系统,需要外部创造条件,具体实现可以为供应商与系统配置映射关系;
  3. Strategy模式中只有具有继承关系的算法类,实际上部分供应商采用了相同的ERP系统,这批系统原生具备了满足商品信息对接的接口,此时将使用同一个策略类,但当有供应商对该接口进行定制化时,此时继承原有策略进行定制即可。

总的来说,策略模式在商品信息对接的场景上完全适用。

编码设计

对于前面讲过,我们将使用策略模式来实现对接功能,先简单画一下UML

UML-策略模式对象关系.png

下面先定义一个策略接口

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与接口地址映射等等,考虑到接口地址与业务无强关联,所以我司选择将其剥离业务系统,在网关上做映射。

除此之外,还有需要零碎的问题,在方案落地前需要解决,详细代码我就不贴了(涉密),本文只做技术与业务结合分析。

总结

策略模式其实业务中可运用的范围非常广,除了以上的“商品信息对接”业务场景,还有“商品库存对接”业务场景也同样适用,除此之外,还有很多通用一点的场景,如在前端“菜单页面待办事项统计”场景(菜单上的小红点),完全可以使用策略模式来实现。

设计模式常见于各种框架、类库中,初衷就是为了重用代码、让代码更容易被他人理解、保证代码可靠性。但是在业务代码中,盲目使用设计模式,往往会适得其反,使业务代码可读性与拓展性大大降低。所以想要在业务代码中运用设计模式,一定要根据业务分析得到结论,再考虑是否使用设计模式。

本文仅代表个人观点,欢迎看官指正。

敬畏技术,忠于业务。