工厂模式+策略模式+反射机制解决系统功能模块相似的问题

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 背景
  • 痛点
  • 一、如何根据不同条件获取不同的数据库Mapper?
  • 二、如何根据不同条件创建相应的数据库实体对象?
  • 总结


背景

本人在项目中遇到了一种情况是,两组功能模块的service层业务逻辑几乎完全相同。只是controller层传入的实体不同,以及Dao层采用的mapper不同(本项目持久层框架采用的是Mybatis-plus)。因此我希望可以使用一套service层代码,通过参数控制获取不同的mapper来实现不同条件的数据库操作,前提是不使用 if -else 这种不符合开闭原则的代码。

痛点

针对这种情况,如果不采用设计模式,就会导致需要编写两套几乎完全相同的代码,工作量非常大,而且扩展性差,如果后续业务需要再增加一组模块,则还需要再编写一套代码,费时费力,代码冗余。因此,本人选择了采用了工程模式+策略模式以及结合了java反射机制,解决了上诉的痛点。下面我就结果我开发时候的设计思路。


提示:以下是结合我个人开发的思路顺序来记录的

一、如何根据不同条件获取不同的数据库Mapper?

上面提到,我们的service层逻辑几乎相同,因此我希望的是可以通过传参数的方式获取不同条件下的mapper来进行数据库操作。前提是不使用if-else这种很low,而且扩展性很差,不符合开闭原则的方式。因此,我首先想到的就是策略模式,我把不同的mapper想象成不同的策略。标准的策略模式类图如下:
工厂模式+策略模式+反射机制解决系统功能模块相似的问题_第1张图片
如果单独使用策略模式的话,实现的类图如下所示,该图是我真正实现的类图,其中IService 为mybatis-plus封装的接口,具体对数据库的操作需要继承IService,会mybatis-plus的同学自然明白,XXXServiceImpl为自己创建的对数据进行操作的实现类,即所谓的mapper,也是我们要获得的策略。
工厂模式+策略模式+反射机制解决系统功能模块相似的问题_第2张图片
但是,单独使用策略模式的时候我发现个问题,就是获取策略的上下文类congtext,需要依赖ISerivce,即策略的父类。这个时候,我想到,是否可以结合工厂模式,通过工厂模式来获取相应的策略类,而不是通过这种上下文依赖的方式来获取策略,这样体现了工厂模式的优势,即用上下文工厂来替代传统策略模式获取策略的方式。因此,按照我的想法我做了类图的设计,对上述类图进行了改进,工厂模式+策略模式结合的类图如下:
工厂模式+策略模式+反射机制解决系统功能模块相似的问题_第3张图片
经过上图的设计,我可以实现传入变量获取相应的数据库操作实现类,代码如下:

IService contractStrategy = ContractStrategyContextFactory.createStrategy(contractType);

工厂类中的方法如下:

 //因为涉密,仅截取部分代码片段
 private static Map<String, ServiceImpl> hashmap = new HashMap<>();

    @PostConstruct
    private void init(){
        hashmap.put(ContractMapperEnum.SERVICE_CONTRACT.getType(),serviceContractInfoService);
        hashmap.put(ContractMapperEnum.SPARE_PARTS_CONTRACT.getType(),sparePartsContractInfoService);
    }

    public static IService createStrategy(String type){
        return hashmap.get(type);
    }

因此,我们需求的第一步就得以实现了,通过不同接口传入的参数不同,调用不同的数据库操作类,并且没有使用任何的if else判断,降低了耦合性,并且满足了开闭原则。但是,获取到的IServie的范型是T,那么问题来了,如何根据接口参数的不同,生成相应的数据库实体对象呢,因为,BeanUtils.copyProperties来复制相应的数据库实体对象时,要先创建好实体类对象,如果问题不解决,那么我们还需要用if else来进行判断,来创建对应数据库的实体类对象。解决方式看第二部分。

二、如何根据不同条件创建相应的数据库实体对象?

首先,我知道java创建对象的方式有三种,分别是 new 、拷贝以及反射。所以,这里我第一时间想到了就是反射的方式,即 Class.forName()获取类,再用newInstance()方法创建对象,但是,这个方法返回的类中需要预先知道类的范型,不然创建的对象全部都是object,无法对数据库实体类对象进行字段的set方法。这时,我想到了继承的方式,将数据库实体类抽象出统一的父类BaseEntity,然后创建子类并用父类来作为反射的接收类,然后再进行实例化,BeanUtils.copyProperties就可以将属性copy到父类对象中,具体实现如下面的代码所示。最后将数据库实体类的父类对象,传入我们通过策略模式获取的数据库操作类,完成最后的数据库操作。此时流程全部结束,实现了我们的最终目的。

//获取数据库实体类,并用父类接收
Class<ContractTempBaseEntity> clazz = (Class<ContractTempBaseEntity>)Class.forName(className);
//实例化
ContractTempBaseEntity contractTempBaseEntity = clazz.newInstance();
//将接口参数copy到数据库实体类对象
BeanUtils.copyProperties(baseContractTempInfoDTO,contractTempBaseEntity);
//将对象传入到通过策略模式获取的数据库操作类,完成最后的数据库操作
contractStrategy.saveOrUpdate(contractTempBaseEntity);

以上涉及到的枚举类如下:

//枚举类例子
SPARE_PARTS_CONTRACT("SparePartsContractInfo", "数据库实体类的类路径,作为class.forName的参数"),
SPARE_PARTS_CONTRACT_TEMP("SparePartsContractTempInfo","数据库实体类的类路径,作为class.forName的参数"),

总结

通过以上方式,基本上完成了需求的痛点,而且可以满足日后的扩展,最终从controller 到service再到Dao层的时序图如下:
工厂模式+策略模式+反射机制解决系统功能模块相似的问题_第4张图片

你可能感兴趣的:(设计模式,策略模式,java,spring)