简介
模板模式是一种基于继承的设计模式,它定义了一个算法的骨架,将某些步骤推迟到子类中实现。
提供给了后续新增功能开发的一个模板,按照既定的模板方法进行开发即可满足某一种业务需求。
不同的事物有相同的行为,可以使用模板模式,比如仓库的单据,有入库单也有出库单,出入库都有操作时间、申请人等。将相同的操作都提取到公共的父类来实现,子类实现自己个性化的部分。
总体的设计主要分为两块:
Abstract Class:抽象类,抽象的公共方法和方法的默认实现
Concrete Class:具体的实现类,继承Abstract Class获得模板方法,当需要自定义方法的具体实现时,通过重写父类的方法实现
Demo
以出入库单据为例子
入库单据设计:分为单据的主表和子表,主表用于存单据的主体信息,如单据号等;子表用于存储单据和物品的关系(入库当然是需要入物品)
in_bill_main(入库单据主表)
in_bill_detail(入库单据子表)
出库单据设计:也分为单据的主表和子表,主表用于存单据的主体信息,如单据号;子表用于存储单据和物品的关系(出库当然也是需要出库物品)
out_bill_main(入库单据主表)
out_bill_detail(入库单据子表)
简单例子
构建抽象父类BaseBillAbstract:
java
package com.admin.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
/**
* @Author: huangjun
* @Date: 2024/6/24 14:24
* @Version: 1.0
* @Description:
*/
public abstract class BaseBillAbstract {
public IPage<BillPage> queryBillPage(){
//TODO
return null;
}
public BillDetail getBillDetail(){
//TODO
return null;
}
...
}
公共页面查询类型BillPage:
java
package com.admin.controller;
import lombok.Data;
/**
* @Author: huangjun
* @Date: 2024/6/24 14:25
* @Version: 1.0
* @Description:
*/
@Data
public class BillPage {
private String billId;
private String billCode;
//TODO 自行扩展属性
}
公共查询详情类BillDetail:
java
package com.admin.controller;
import lombok.Data;
/**
* @Author: huangjun
* @Date: 2024/6/24 14:26
* @Version: 1.0
* @Description:
*/
@Data
public class BillDetail {
private String billId;
private String billCode;
//TODO 自行扩展属性
}
入库单据的实现InBillMainService:
java
package com.admin.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
/**
* @Author: huangjun
* @Date: 2024/6/24 14:29
* @Version: 1.0
* @Description:
*/
public class InBillMainService extends BaseBillAbstract{
@Override
public IPage<BillPage> queryBillPage() {
//根据需要重写,不重写则使用父类的模板方法
return super.queryBillPage();
}
}
出库单据的实现OutBillMainService:
java
package com.admin.controller;
/**
* @Author: huangjun
* @Date: 2024/6/24 14:31
* @Version: 1.0
* @Description:
*/
public class OutBillMainService extends BaseBillAbstract {
@Override
public BillDetail getBillDetail() {
//根据需要重写,不重写则使用父类的模板方法
return super.getBillDetail();
}
}
至此,简单的模板设计模式已经实现了,根据需要重写父类的模板方法。
贴合业务与MybatisPlus整合
注:这里分了主表和子表,完全是根据业务来,如果没有两张表的结构,泛型的设计使用一个即可。这里设计两个泛型,是因为业务中设计了两张表,并且需要同时对两张表进行操作
主表的抽象父类
在实际开发中,我们的项目是跟Myabtis或MybatisPlus整合的,具体的实现类是继承了Mybatis的父类。由于java单继承的缘故,无法再继承抽象模板父类。这里以MybatisPlus举例,我们希望继承抽象父类的同时也能保留MybatisPlus的方法,这就需要改造我们的模板父类来实现。
定义主表的接口,继承IService,并传入两个泛型对象(由于使用到了两张表,单表的设计一个泛型即可)
java
package com.admin.template;
import com.admin.bean.dto.BaseBill;
import com.admin.bean.dto.BaseBillDetail;
import com.admin.bean.dto.BillDetail;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Map;
/**
* @Author: huangjun
* @Date: 2024/5/14 8:47
* @Version: 1.0
* @Description:
*/
public interface IBaseBillService<T extends BaseBill,D extends BaseBillDetail> extends IService<T> {
//分页查询
IPage<BaseBill> selectBillPage(Map<String, Object> map);
//查询详情
BillDetail getBillDetail(String id);
//删除单据
Boolean deleteBill(List<Long> billIds);
//保存单据
Boolean saveBill(Map<String, Object> map);
}
BaseBill类只是一个标识,标识主表需要继承BaseBill,同样,子表也需要继承BaseBillDetail,对模板的实现类进行限制。
实现主表的接口,并实现IBaseBillService类:
java
package com.admin.template;
import com.admin.bean.dto.BaseBill;
import com.admin.bean.dto.BaseBillDetail;
import com.admin.bean.dto.BillDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List;
import java.util.Map;
/**
* @Author: huangjun
* @Date: 2024/5/13 14:58
* @Version: 1.0
* @Description:
*/
public abstract class BaseBillServiceImpl<M extends BaseMapper<T>, T extends BaseBill,D extends BaseBillDetail> extends ServiceImpl<M, T> implements IBaseBillService<T,D> {
@Override
public IPage<BaseBill> selectBillPage(Map<String, Object> map) {
//TODO 模板实现方式
return null;
}
@Override
public BillDetail getBillDetail(String id) {
//TODO 模板实现方式
return null;
}
@Override
public Boolean deleteBill(List<Long> billIds) {
//TODO 模板实现方式
return null;
}
@Override
public Boolean saveBill(Map<String, Object> map) {
//TODO 模板实现方式
return save();
}
}
这里我们继承了MybatisPuls的ServiceImpl,同时实现了我们定义的主表的接口,对这些接口进行一些模板实现。
子表的抽象父类
单表的抽象类可以参考
子表的抽象父类也是同主表的抽象父类相同,这里不再赘述
java
package com.admin.template;
import com.admin.bean.dto.BaseBillDetail;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @Author: huangjun
* @Date: 2024/5/14 9:14
* @Version: 1.0
* @Description:
*/
public interface IBaseBillDetailService<T extends BaseBillDetail> extends IService<T> {
//TODO 子表的接口
}
子表抽象父类
java
package com.admin.template;
import com.admin.bean.dto.BaseBillDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
/**
* @Author: huangjun
* @Date: 2024/5/13 16:27
* @Version: 1.0
* @Description:
*/
public abstract class BaseBillDetailServiceImpl<M extends BaseMapper<T>,T extends BaseBillDetail> extends ServiceImpl<M, T> implements IBaseBillDetailService<T> {
//TODO 子表接口的模板实现方法
}
主表实现抽象父类
主表的实体类继承BaseBill
因为抽象接口中规范了泛型的类型
java
package com.admin.entity;
import com.admin.bean.dto.BaseBill;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* out_bill_main
* </p>
*
* @author huangjun
* @since 2024-06-24
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("out_bill_main")
public class OutBillMain extends BaseBill implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 单据id
*/
private String billId;
/**
* 单据编号
*/
private String billCode;
/**
* 入库说明
*/
private String outInfo;
/**
* 创建人
*/
private String createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
主表接口继承抽象父类的接口
java
package com.admin.service;
import com.admin.entity.OutBillDetail;
import com.admin.entity.OutBillMain;
import com.admin.template.IBaseBillService;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* out_bill_main 服务类
* </p>
*
* @author huangjun
* @since 2024-06-24
*/
public interface IOutBillMainService extends IBaseBillService<OutBillMain, OutBillDetail> {
}
主表接口实现类继承抽象父类接口
java
package com.admin.service.impl;
import com.admin.entity.OutBillDetail;
import com.admin.entity.OutBillMain;
import com.admin.mapper.OutBillMainMapper;
import com.admin.service.IOutBillMainService;
import com.admin.template.BaseBillServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* out_bill_main 服务实现类
* </p>
*
* @author huangjun
* @since 2024-06-24
*/
@Service
public class OutBillMainServiceImpl extends BaseBillServiceImpl<OutBillMainMapper, OutBillMain, OutBillDetail> implements IOutBillMainService {
}
子表实现抽象父类
子表的实体类继承BaseBillDetail
因为抽象接口中规范了泛型的类型
java
package com.admin.entity;
import com.admin.bean.dto.BaseBillDetail;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* out_bill_detail
* </p>
*
* @author huangjun
* @since 2024-06-24
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("out_bill_detail")
public class OutBillDetail extends BaseBillDetail implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 详情iid
*/
private String billDetailId;
/**
* 单据id
*/
private String outBillId;
/**
* 货物id
*/
private String cargoId;
/**
* 货物数量
*/
private String cargoNum;
/**
* 创建人
*/
private String createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 更新时间
*/
private Date updatedTime;
}
子表的接口继承子表抽象接口
java
package com.admin.service;
import com.admin.entity.OutBillDetail;
import com.admin.template.IBaseBillDetailService;
/**
* <p>
* out_bill_detail 服务类
* </p>
*
* @author huangjun
* @since 2024-06-24
*/
public interface IOutBillDetailService extends IBaseBillDetailService<OutBillDetail> {
}
子表接口实现类继承子表抽象接口实现类
java
package com.admin.service.impl;
import com.admin.entity.OutBillDetail;
import com.admin.mapper.OutBillDetailMapper;
import com.admin.service.IOutBillDetailService;
import com.admin.template.BaseBillDetailServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* out_bill_detail 服务实现类
* </p>
*
* @author huangjun
* @since 2024-06-24
*/
@Service
public abstract class OutBillDetailServiceImpl extends BaseBillDetailServiceImpl<OutBillDetailMapper, OutBillDetail> implements IOutBillDetailService {
}
至此,就完成了既继承了抽象父类,又保留了MybatisPlus的自带的方法
如果对于模板方法有细微的调整,如对参数的一些转换,需要对模板方法进行一些细微的改动,但是主体的代码逻辑和代码书写都没有改动,那么可以对抽象父类的模板方法做一些改动,添加一些前置或后置方法:
java
package com.admin.template;
import com.admin.bean.dto.BaseBill;
import com.admin.bean.dto.BaseBillDetail;
import com.admin.bean.dto.BillDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List;
import java.util.Map;
/**
* @Author: huangjun
* @Date: 2024/5/13 14:58
* @Version: 1.0
* @Description:
*/
public abstract class BaseBillServiceImpl<M extends BaseMapper<T>, T extends BaseBill,D extends BaseBillDetail> extends ServiceImpl<M, T> implements IBaseBillService<T,D> {
@Override
public IPage<BaseBill> selectBillPage(Map<String, Object> map) {
beforSelectBillPage(map);
//TODO 模板实现方式
IPage<BaseBill> baseBillIPage = ...;
afterSelectBillPage(iPage);
return null;
}
@Override
public BillDetail getBillDetail(String id) {
//TODO 模板实现方式
return null;
}
@Override
public Boolean deleteBill(List<Long> billIds) {
//TODO 模板实现方式
return null;
}
@Override
public Boolean saveBill(Map<String, Object> map) {
//TODO 模板实现方式
return save();
}
//分页查询前置方法,对参数进行处理后再执行分页查询
public void beforSelectBillPage(Map<String, Object> map){}
//分页查询后置方法,对分页查询的结果进行处理后再返回
public void afterSelectBillPage(IPage<BaseBill> baseBillIPage){}
}
当实现类需要对参数做处理时,重写父类的beforSelectBillPage方法即可,不用再写一遍父类的模板方法。以此类推,可以为所以的模板实现方法添加这些前置或后置的方法,实现类根据需要去重写。
模板调度器
我们有了很多的模板实现子类,那该如何去选择对于实现类处理?这时就需要设计一个调度器去选择对应的模板实现类。
java
package gov.df.gwc.service.bill.config;
import gov.df.gwc.service.bill.base.impl.BaseBillServiceImpl;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: huangjun
* @Date: 2024/5/14 11:09
* @Version: 1.0
* @Description: 单据服务器调度器
*/
@Component
public class DispatchBillService {
//map保存所有的模板实现类,key为模板的编码
private Map<String, BaseBillServiceImpl> billServiceMap = new HashMap<>();
//对外提供一个注册模板的方法
public void registerBillService(String billType ,BaseBillServiceImpl service){
billServiceMap.put(billType,service);
}
//对外提供一个获取模板实现类的方法
public BaseBillServiceImpl getBillService(String billType){
BaseBillServiceImpl baseBillService = billServiceMap.get(billType);
Assert.notNull(baseBillService,"没有对应的单据类型");
return baseBillService;
}
}
将模板实现添加到调度器中,可以通过Springboot启动后去Spring容器中获取到不同的bean添加到调度器中。这里选择在抽象父类的构造方法中就添加到调度起中。
为抽象父类添加构造方法:
java
package com.admin.template;
import com.admin.bean.dto.BaseBill;
import com.admin.bean.dto.BaseBillDetail;
import com.admin.bean.dto.BillDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* @Author: huangjun
* @Date: 2024/5/13 14:58
* @Version: 1.0
* @Description:
*/
public abstract class BaseBillServiceImpl<M extends BaseMapper<T>, T extends BaseBill,D extends BaseBillDetail> extends ServiceImpl<M, T> implements IBaseBillService<T,D> {
private IBaseBillDetailService<D> detailService;
public BaseBillServiceImpl(String billType,DispatchBillService dispatchBillService,IBaseBillDetailService<D> detailService) {
this.dispatchBillService = dispatchBillService;
this.detailService = detailService;
//将当前模板实现类添加到调度器
dispatchBillService.registerBillService(billType,this);
}
@Override
public IPage<BaseBill> selectBillPage(Map<String, Object> map) {
beforSelectBillPage(map);
return null;
}
@Override
public BillDetail getBillDetail(String id) {
//TODO 模板实现方式
return null;
}
@Override
public Boolean deleteBill(List<Long> billIds) {
//TODO 模板实现方式
return null;
}
public void beforSelectBillPage(Map<String, Object> map){}
public void afterSelectBillPage(IPage<BaseBill> baseBillIPage){}
}
当子类继承抽象父类时,将单据类型传入:
java
package com.admin.service.impl;
import com.admin.entity.OutBillDetail;
import com.admin.entity.OutBillMain;
import com.admin.mapper.OutBillMainMapper;
import com.admin.service.IOutBillMainService;
import com.admin.template.BaseBillServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* out_bill_main 服务实现类
* </p>
*
* @author huangjun
* @since 2024-06-24
*/
@Service
public class OutBillMainServiceImpl extends BaseBillServiceImpl<OutBillMainMapper, OutBillMain, OutBillDetail> implements IOutBillMainService {
//Spring容器会注入这些bean
public OutBillMainServiceImpl(DispatchBillService dispatchBillService, IBaseBillDetailService<OutBillDetail> detailService) {
super("OUT",dispatchBillService,detailService);
}
}
这样,就会自动将当前的模板实现类自动添加到调度器中,当需要的时候,根据类型去调度器中获取到对应的实现类:
java
//从调度器中根据单据类型获取到对应的模板实现类
BaseBillServiceImpl billService = dispatchBillService.getBillService(billType);
billService.getBillDetail(billId);
注意:抽象父类是不归Spring容器管理的,如果在抽象父类中需要使用Spring容器中的bean的时候,需要通过子类的构造方法中传入。
写在最后
模板模式可以为我们省略了很多重复的代码,如果后续新新增单据(例子),可以通过继承抽象父类快速集成到业务中。对于需要自定义处理的接口,也可以重写父类的模板方法来实现,既保证了可维护性,也保证了可拓展性。
添加模板调度器是为了迎合业务,为不同的单据类型(例子)选择不同的模板实现,并通过构造方法的方式添加模板实现类到调度器。实现类主要专注是否重写父类方法,简化模板实现类的代码。
模板模式可以配合责任链模式来使用来达到更好的效果。