利用java三大特性和工厂模式,实现项目中实现类的多态化

243 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

前言

在项目中,往往会遇到,一个接口需要不同的业务实现类的,在引用时,往往会因为一个接口有多个实现类导致接口引入扩展性比较差。

假设业务场景,物流下单,用不同的物流发货

一、定义接口

首先定义一个通用接口,然后每个物流公司去实现该接口:

public interface WuliuDirectService {
    /**
     * 物流公司类型
     * @return
     */
    public String getType();
    /**
     * 下单
     * @return
     */
    public void createOrder(ElectronicFaceSheetReq faceSheetReq);

    /**
     * 查询订单
     * @param orderNo 订单号
     * @return
     */
    public String queryOrder(String orderNo);

    /**
     * 物流状态更新
     * @param req
     * @param resp
     */
    public void traceCallBack(HttpServletRequest req, HttpServletResponse resp);

    /**
     * 取消订单
     * @param demand
     * @return
     */
    public void cancelOrder(LogisticsDemand demand);

    /**
     * 查询订单轨迹
     * @param orderNo 订单号
     * @return
     */
    public OrderMapRes traceMap(String orderNo);
}

接口中,getType方法是为了区分不同的物流公司,其它的几个方法都是物流公司的通用方法,下单,物流状态更新,轨迹查询等方法。不同的物流公司可能实现的方法不同,可根据实际实现方法在各自的实现类中实现。

有人会问,getType方法有什么用?别着急,慢慢往下看。

二、各个物流公司实现类

所有的物流公司需要实现上述接口,定义好不同的物流公司getType的值,以下列出四通一达实现类的getType方法,其他业务方法暂不赘述。

@Service
public class YtServiceImpl implements WuliuDirectService {
    /**
    * 圆通
    *
    **/
    @Override
    public String getType() {
        return Constant.YT_CODE;
    }

}
@Service
public class ZtServiceImpl implements WuliuDirectService {
    /**
    * 中通
    *
    **/
    @Override
    public String getType() {
        return Constant.ZT_CODE;
    }

}
@Service
public class StServiceImpl implements WuliuDirectService {
    /**
    * 申通
    *
    **/
    @Override
    public String getType() {
        return Constant.ST_CODE;
    }

}
@Service
public class YdServiceImpl implements WuliuDirectService {
    /**
    * 韵达
    *
    **/
    @Override
    public String getType() {
        return Constant.YD_CODE;
    }

}

这样,不同的WuliuDirectService实现类就可以根据getType区分。

三、最简单的引用方式

在不同的物流公司实现类实现接口后,我们可以在controller中,把所有的实现类引入进来,然后根据需求,选择不同的物流公司实现类进行业务处理,代码如下:

@RestController
@RequestMapping("/logistics-demand")
public class WuliuDirectController {

    @Resource
    private StServiceImpl stSerivce;

    @Resource
    private YtServiceImpl ytSerivce;
    @Resource
    private ZtServiceImpl ztSerivce;

    @Resource
    private YdServiceImpl ydSerivce;
    @Resource
    private ILogisticsDemandService logisticsDemandService;
    @Resource
    private LogisticsDemandMapper logisticsDemandMapper;
    
     /**
     *电子面单
    *@param 
    * @return
     **/
    @PostMapping("/createOrder")
    public Response createOrder(@RequestBody  @Valid ElectronicFaceSheetReq e){
        if(StringUtils.isNotBlank(e.getShipperCode())){
            String shipperCode = e.getShipperCode();
            if(Constant.YT_CODE.equals(shipperCode)){
                ytService.createOrder(e);
           }else if(Constant.ST_CODE.equals(shipperCode)){
                stService.createOrder(e);
           }else if(Constant.ZT_CODE.equals(shipperCode)){
                ztService.createOrder(e);
           }else if(Constant.YD_CODE.equals(shipperCode)){
                ydService.createOrder(e);
           }else{
                return Response.fail(BusinessStatusEnum.UNSUPPORTED_SHOPPING.getMessage(), HttpStatus.ERROR);
            }
            return Response.success(BusinessStatusEnum.PLACE_SUCCESS.getMessage(), HttpStatus.SUCCESS);
          }
    }

这样做的问题是,扩展性差,如果再加入一个顺丰快递,controller中的代码改动比较大。那么怎么解决呢?

四、利用工厂模式,将不同的实现类初始化到工厂中

第一步中提到的疑问,getType这个方法在这里就起到作用了,思路是,在容器初始化时,springboot会扫描@service注解下的所有service,那么我们可以在此时,将不同的service根据getType,以键值对的方式存入内存中。这样,在业务实现时就可以根据getType不同的值拿到不同的物流实现方式。具体实现代码如下:

@Component
public class ServiceFactory implements InitializingBean {

    private static final Map<String,WuliuDirectService> routerMap = Maps.newHashMap();

    @Autowired
    private List<WuliuDirectService> iServiceList;

    @Override
    public void afterPropertiesSet() throws Exception {
        for (WuliuDirectService service : iServiceList) {
            String type = service.getType();
            if(routerMap.containsKey(type)){
                throw new BusinessException("物流公司"+type+"对应的service冲突");
            }
            routerMap.put(type, service);
        }
        System.out.println(routerMap);
    }

    public WuliuDirectService getService(String type) {
        return routerMap.get(type);
    }
}

这样,我们使用时就可以根据传过来的物流公司代码获取到相应的物流公司实现类,进而进行对应的业务处理。

@RestController
@RequestMapping("/logistics-demand")
public class WuliuDirectController {

    @Resource
    private ServiceFactory serviceFactory;
    @Resource
    private ILogisticsDemandService logisticsDemandService;
    @Resource
    private LogisticsDemandMapper logisticsDemandMapper;

     /**
     *电子面单
    * @param
    * @return
     **/
    @PostMapping("/createOrder")
    public Response createOrder(@RequestBody @Valid ElectronicFaceSheetReq e){
        if(StringUtils.isNotBlank(e.getShipperCode())){
            String shipperCode = e.getShipperCode();
            WuliuDirectService service = serviceFactory.getService(shipperCode);
            if(service != null){
                service.createOrder(e);
                return Response.success(BusinessStatusEnum.PLACE_SUCCESS.getMessage(), HttpStatus.SUCCESS);
            }
          }
        return Response.fail(BusinessStatusEnum.UNSUPPORTED_SHOPPING.getMessage(), HttpStatus.ERROR);
    }

而不用在controller中引入所有的物流公司实现类。

小结

利用工厂模式,将同一接口的不同实现类初始化到工厂类中,在使用时通过工厂类取相应的实现类。这样做的好处是不用反复修改代码,在实现类增加或删除时,不用过多的修改,符合了开闭原则。