文章目录
1.项目背景
业务涉及三种指标采集模型:公共采集模型、插件采集模型、自主上报模型。这些模型存在以下特点:
在数据结构方面,都包含指标字段、模型token、模型名称、标签等信息。其他信息则随着模型不同而不同。
在后台实现方面,都包含增删改查等方法,且每个方法存在通用代码。
由于存在诸多相通之处,所以考虑使用设计模式来提高代码维护性和拓展性。
2.设计模式的体现
2.1 设计过程
2.1.1 数据结构设计
考虑到三种模型数据结构存在相同之处,于是后台定义一个父类,包含公共属性,三个子类继承父类,再扩展私有的属性。
数据结构如下:
2.1.2 controller层设计
常规做法是
针对每个模型分别写自己的controller,在controller分别定义CRUD接口提供给前端。
这种做法也不是不可行,只是这样一来,因为都有增删改方法,容易造成重复代码。
所以,改进做法是
考虑定义一个父类controller,包含公用的CRUD接口提供给前端,然后子类继承该controller。
如下:
这样一来,如果以后再加一种新的采集模型类型,那么直接继承父类controller即可,方便扩展。
2.1.3 service层设计
三种模型的CRUD接口存在通用的逻辑,于是考虑使用模板设计模式,将通用逻辑定义在模板方法里,钩子方法交给具体模型子类去实现。
如下,AbstractCollectModelServiceImpl
抽象类定义了模板方法,钩子方法则定义为抽象方法,交给子类实现。
如果以后再加一种新的采集模型类型,那么直接继承AbstractCollectModelServiceImpl
即可。
需要注意的是,抽象类上面还实现一个了接口CollectModelService
,这是为了规范化java的接口式编程。
2.1.4 门面层设计
在上面的设计过程中,如果我们直接在controller中注入并访问service,那么存在的问题是:
我们访问的是父类controller,但父类中到底要注入哪一个模型子类的service呢(如果没有父类controller,而是有各自的controller,那么可以直接在controller中注入实际的子类service,此时就不存在此问题)?
所以,需要一个地方维护每个模型service的信息,前端访问controller时,能够从维护的地方找到对应的模型service。
于是引入了一个门面,其中维护了每个模型类型和模型对应的子类service, 如下:
CollectModelFacade
类有一个map类型的成员,key为模型类型(String),value为实际的子类。
当前端访问controller时会附模型类型参数,后台根据该参数匹配到门面中的map的key,然后就能找到实际的子类。
2.2 整体请求链路
链路为:
controller->门面->抽象父类->实际子类
3.泛型的体现
3.1 controller层
3.2 门面层
门面用map来存放采集模型子类的信息
private ConcurrentHashMap<String, CollectModelService> baseCollectModelServiceMap = new ConcurrentHashMap<>();
项目启动时,往map中放入实际子类
public void afterPropertiesSet() {
//将采集模型增删改的基础service放入map
Map<String, CollectModelService> baseServiceMap = context.getBeansOfType(CollectModelService.class);
for (String serviceName : baseServiceMap.keySet()) {
baseCollectModelServiceMap.putIfAbsent(baseServiceMap.get(serviceName).getCollectType(), baseServiceMap.get(serviceName));
}
}
其中CollectModelService稍后介绍。
门面要对外提供方法入口,以添加采集模型方法为例
public Boolean addModel(BaseCollectModelDTO dto){
return baseCollectModelServiceMap.get(dto.getCollectType()).addModel(dto);
}
3.3 service层
由于存在三种采集模型的数据结构,所以CollectModelService
中,方法的参数或者返回值就不能定义死,而是要使用泛型,以动态接收实际的数据结构。
以addModel方法为例
public interface CollectModelService<T> extends CollectModelTypeService{
boolean addModel(T t);
......
......
}
接口上声明了泛型T,在addModel方法参数中使用了T。
正常来说,由于AbstractCollectModelServiceImpl
类实现了接口,所以需要为泛型传入实际的类型。如下,指定泛型类型为BaseCollectModelDTO
public class AbstractCollectModelServiceImpl implements CollectModelService<BaseCollectModelDTO> {
//钩子方法
public abstract void save(BaseCollectModelDTO dto, Integer collectModelId);
//模板方法
@Override
public boolean addModel(BaseCollectModelDTO baseCollectModelDTO) {
......
save(baseCollectModelDTO, collectModelId);
......
return true;
}
}
这样的话,子类继承AbstractCollectModelServiceImpl
类并实现save方法时,方法参数不得不也采用BaseCollectModelDTO
类型。以CommonCollectModelServiceImpl
为例,此时它的save变成如下
public class CommonCollectModelServiceImpl extends AbstractCollectModelServiceImpl {
@Override
public void save(BaseCollectModelDTO m, Integer collectModelId) {
}
}
如此一来,具体子类的方法参数都是BaseCollectModelDTO
(虽然实际传入的是具体子类DTO),就导致子类DTO中特有的属性无法访问到了,业务逻辑也就无法实现。
所以,正确做法是
AbstractCollectModelServiceImpl
类不传具体的泛型类型,而是也定义一个泛型。
如下,声明M类型,上边界为BaseCollectModelDTO
public abstract class AbstractCollectModelServiceImpl<M extends BaseCollectModelDTO> implements CollectModelService<M>{
public boolean addModel(M m) {
......
save(m, collectModelId);
......
return true;
}
}
具体子类实现如下,
@Service
public class CommonCollectModelServiceImpl extends AbstractCollectModelServiceImpl<CommonCollectModelDTO> {
public void save(CommonCollectModelDTO commonCollectModelDTO, Integer collectModelId){
}
}
这样就解决了上面的问题。