设计模式之模板模式

简介

模板模式是一种基于继承的设计模式,它定义了一个算法的骨架,将某些步骤推迟到子类中实现

提供给了后续新增功能开发的一个模板,按照既定的模板方法进行开发即可满足某一种业务需求。

不同的事物有相同的行为,可以使用模板模式,比如仓库的单据,有入库单也有出库单,出入库都有操作时间、申请人等。将相同的操作都提取到公共的父类来实现,子类实现自己个性化的部分。

总体的设计主要分为两块:

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的时候,需要通过子类的构造方法中传入。

写在最后

模板模式可以为我们省略了很多重复的代码,如果后续新新增单据(例子),可以通过继承抽象父类快速集成到业务中。对于需要自定义处理的接口,也可以重写父类的模板方法来实现,既保证了可维护性,也保证了可拓展性。

添加模板调度器是为了迎合业务,为不同的单据类型(例子)选择不同的模板实现,并通过构造方法的方式添加模板实现类到调度器。实现类主要专注是否重写父类方法,简化模板实现类的代码。

模板模式可以配合责任链模式来使用来达到更好的效果。

相关推荐
信徒_1 小时前
常用设计模式
java·单例模式·设计模式
lxyzcm19 小时前
深入理解C++23的Deducing this特性(上):基础概念与语法详解
开发语言·c++·spring boot·设计模式·c++23
越甲八千20 小时前
重温设计模式--单例模式
单例模式·设计模式
Vincent(朱志强)20 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
诸葛悠闲21 小时前
设计模式——桥接模式
设计模式·桥接模式
捕鲸叉1 天前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
小小小妮子~1 天前
框架专题:设计模式
设计模式·框架
先睡1 天前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
Damon_X1 天前
桥接模式(Bridge Pattern)
设计模式·桥接模式
越甲八千2 天前
重温设计模式--享元模式
设计模式·享元模式