JAVA门面和模板设计模式在项目中的体现

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层

参考 springmvc方法传父类型参数,无法映射到子类型

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){

	}
}

这样就解决了上面的问题。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值