设计模式五:模板模式

1、模板方法

定义一个主线流程业务逻辑(abstract类),将共用的方法在父类中实现,不共用的方法定义为抽象方法(abstract方法),在不同子类中进行实现。从而达到子类可以不改变该父类方法主流程的情况下重定义某些特定步骤。它是一种类行为型模式。

父类定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。或者通俗点讲,处理某个流程的代码已经都具备,当其中某个节点的代码不能确定,因此我们采用模板模式,将这个节点的代码实现转移给子类完成;

例:购买汽车;在不同品牌汽车的4S店购买汽车都要经历相同的过程,进店—>销售讲解—>试驾—>支付购买—>开车回家,

每一家车店都是这种逻辑,那么可以将这套逻辑抽出来,并将这个一套逻辑写成一个方法,即模板方法,使用public final修饰

其中每一步都是一个方法,通用的步骤在模板类中将该方法写好,如进店、讲解、试驾和开车回家, 使用private修饰

不同的逻辑,如支付购买(价格不同)则为抽象方法,在子类中再写,使用protect修饰

也可以有一些回调函数,使用使用public修饰

//汽车4S店抽象类
public abstract class Car4STemplate {

    //进店
    private void in(){
        System.out.println("大步跨过4S店门......");
    }

    //销售讲解
    private void explanation(){
        System.out.println("销售:请务必购买~~~");
    }

    //试驾
    private void testDrive(){
        System.out.println("感觉不错......");
    }

    /**
     *  支付购买
     * 【钩子方法】可以为抽象方法,子类必须实现。
     *  也可以是空的非抽象方法 protected void pay() {};
     */
    protected abstract void pay();


    //开车回家
    private void goHome(){
        System.out.println("开着心爱的小车车回家......");
    }

    /**
     *  回调函数,可以先占着这个位置,具体需要的时候,子类可以调用。
     *  在回调里面处理些问题
     */
    public void callback(){
        System.out.println("有没有落东西在4S店......");
    }

    //模板方法
    public final void buyCar(){
        in();
        explanation();
        testDrive();
        pay();
        goHome();
        callback();
    }
}
//宝马4S店子类
public class BMW4S extends Car4STemplate{
    @Override
    protected void pay() {
        System.out.println("华晨宝马X5,落地价78.5W......");
    }
}

//奔驰4S店子类
public class MB4S extends Car4STemplate{
    @Override
    protected void pay() {
        System.out.println("梅赛德斯奔驰-大G,落地价162.40W......");
    }
}
//客户端
public class Client {
    public static void main(String[] args) {
//        Car4STemplate car4S = new BMW4S();
//        car4S.buyCar();


//        Car4STemplate car4S = new MB4S();
//        car4S.buyCar();

        Car4STemplate car4S = new Car4STemplate() {
            @Override
            protected void pay() {
                System.out.println("奥迪A6,落地价65.68W......");
            }

            @Override
            public void callback() {
                System.out.println("孩子忘在4S店了......");
            }
        };
        car4S.buyCar();
    }
}

2、模板模式和工厂模式

项目中多处用到异步导出Excel的功能,步骤为生成本地文件—>上传到OSS—>保存关联记录到数据库—>删除本地文件,项目所有位置的导出功能都是这一套逻辑,很明显应该使用模板方法;但是整个功能又用上了简单工厂模式

// 工厂模式公共接口
public interface IRecordHandler {

    String handleExportAndSaveFileToTempFile(Object fileExportDTO, Long userId,Integer rows);

    String getOssPathPrefix();

}
//实现类
public class DefaultRecord implements IRecordHandler {
    @Override
    public String handleExportAndSaveFileToTempFile(Object dto, Long userId,Integer rows) {
        return null;
    }
    @Override
    public String getOssPathPrefix() {
        return "defaultRecord";
    }
}
public class ModbusLogRecord implements IRecordHandler {
    @Override
    public String handleExportAndSaveFileToTempFile(Object dto, Long userId,Integer rows) {
        //不同的处理逻辑
        return null;
    }
    @Override
    public String getOssPathPrefix() {
        return "modbusLog";
    }
}
public class OtaTaskRecord implements IRecordHandler {
    @Override
    public String handleExportAndSaveFileToTempFile(Object dto, Long userId,Integer rows) {
        //不同的处理逻辑
        return null;
    }
    @Override
    public String getOssPathPrefix() {
        return "otaTaskExport";
    }
}


//工厂类
public class RecordHandleFactory {
    private ModbusLogRecord modbusLogRecord;
    private OtaTaskRecord otaTaskRecord;
    private DefaultRecord defaultRecord;
    @Autowired
    public void setModbusLogRecord(ModbusLogRecord modbusLogRecord) {
        this.modbusLogRecord = modbusLogRecord;
    }
    @Autowired
    public void setOtaTaskRecord(OtaTaskRecord otaTaskRecord) {
        this.otaTaskRecord = otaTaskRecord;
    }
    @Autowired
    public void setDefaultRecord(DefaultRecord defaultRecord) {
        this.defaultRecord = defaultRecord;
    }

    public IRecordHandler getHandle(FileRecordBizType recordType) {
        switch (recordType) {
            case MODBUS_LOG:
                return modbusLogRecord;
            case OTA_UPGRADE_TASK:
                return otaTaskRecord;
            default:
                return defaultRecord;
        }
    }
}


//整个方法就是一个模板
//在recordHandleFactory.getHandle中根据传入的参数生成不同的handler
public void exportToOss(FileExportVO exportVO) {
     String tempFile = null;
     try {
         IRecordHandler recordHandler = recordHandleFactory.getHandle(exportVO.getRecordType());
         //处理导出查询生成本地文件
         tempFile =recordHandler.handleExportAndSaveFileToTempFile(exportVO.getExportDto(),exportVO.getUserId(),exportVO.getRows());
         String ossPathPrefix = recordHandler.getOssPathPrefix();
         //上传文件,生成文件记录
         IotpFileDO fileDO = uploadAndSaveFile(ossPathPrefix,tempFile, exportVO.getFileName(), exportVO.getProjectId(), exportVO.getUserId());
         //保存关联表
         fileBizService.saveFileBiz(fileDO.getId(), exportVO.getFileBizType(), exportVO.getRecordId());
         //更新导出文件记录
         updateFileById(EquipmentExportStatusTypeEnum.CREATED.getCode(), FileUtil.getFileSize(tempFile), exportVO.getRecordId());
     } catch (Exception e) {
         log.error("exportToOss", e);
         updateFileStatusById(EquipmentExportStatusTypeEnum.FAIL.getCode(), exportVO.getRecordId());
     } finally {
         if (tempFile != null) {
             FileUtil.deleteFile(new File(tempFile));
         }
     }
 }

看完代码,比较纠结的是,这个到底属于工厂模式(不应该叫做工厂模式,因为简单工厂模式不属于23中设计模式之一),还是模板模式?

我们要明确一点,设计模式属于逻辑上的结构,它的存在和数据结构中的逻辑结构一样,是一种逻辑,不需要过于极端地要求必须是什么样的格式,一般我们都是采用最适合的方式来写代码。就比如这里为什么不用工厂方法模式或者抽象工厂模式,因为这里一共三种实现类,编写起来清晰且不麻烦,当实现类达到一定数量的时候,再添加的时候会显得比较杂乱,这个时候就可以考虑工厂方法模式了; 再比如模板模式,只要是多个类需要操作的逻辑相同,我们把这个逻辑抽象出来,就属于模板模式

其次,工厂模式属于创建性创建类设计模式,模板模式属于行为类设计模式,他们经常是搭配使用,创建完了之后,在进行固定囖记得操作

所以,设计模式不是独立的存在,一般是进行搭配使用才能产生意外的效果

注:上述模板方法中,若有一些handler会有一些特殊处理,可以使用回调函数进行处理,参照下面spring中的模板模式

3、spring中的模板模式

Spring模板方法模式实质:是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式。Spring几乎所有的外接扩展都采用这种模式。我们先来看看JdbcTemplate的类图
在这里插入图片描述

在JdbcOperations接口中定义execute抽象方法
在这里插入图片描述

在JdbcTemplate中实现了execute方法

@Override
public  T execute(StatementCallback action) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");

    Connection con = DataSourceUtils.getConnection(getDataSource());
    Statement stmt = null;
    try {
        Connection conToUse = con;
        if (this.nativeJdbcExtractor != null &&
            this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
            conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
        }
        stmt = conToUse.createStatement();
        applyStatementSettings(stmt);
        Statement stmtToUse = stmt;
        if (this.nativeJdbcExtractor != null) {
            stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
        }
        T result = action.doInStatement(stmtToUse);
        handleWarnings(stmt);
        return result;
    }
    catch (SQLException ex) {
        // Release Connection early, to avoid potential connection pool deadlock
        // in the case when the exception translator hasn't been initialized yet.
        JdbcUtils.closeStatement(stmt);
        stmt = null;
        DataSourceUtils.releaseConnection(con, getDataSource());
        con = null;
        throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
    }
    finally {
        JdbcUtils.closeStatement(stmt);
        DataSourceUtils.releaseConnection(con, getDataSource());
    }
}

在上面的方法中,一些通用的建立链接,创建Statement对象,释放Statement对象,关闭链接代码都是一些固定的逻辑。实际有变化的逻辑都在T result = action.doInStatement(stmtToUse);中注意看这里的action,是一个回调对象,我们可以看下这个对象的定义

public interface StatementCallback {
	T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}

可以看下这里execute(StatementCallback action)
在这里插入图片描述
我们到UpdateStatementCallback中去看下

@Override
public int update(final String sql) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    if (logger.isDebugEnabled()) {
        logger.debug("Executing SQL update [" + sql + "]");
    }

    class UpdateStatementCallback implements StatementCallback, SqlProvider {
        @Override
        public Integer doInStatement(Statement stmt) throws SQLException {
            int rows = stmt.executeUpdate(sql);
            if (logger.isDebugEnabled()) {
                logger.debug("SQL update affected " + rows + " rows");
            }
            return rows;
        }
        @Override
        public String getSql() {
            return sql;
        }
    }

    return execute(new UpdateStatementCallback());
}

可以看到UpdateStatementCallback 实现了StatementCallback接口,将具体update操作逻辑放在了execute外层,这样就是将变化作为回调对象传入到execute方法中。可以到QueryStatementCallback 看下,亦是同理

@Override
public  T query(final String sql, final ResultSetExtractor rse) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    Assert.notNull(rse, "ResultSetExtractor must not be null");
    if (logger.isDebugEnabled()) {
        logger.debug("Executing SQL query [" + sql + "]");
    }

    class QueryStatementCallback implements StatementCallback, SqlProvider {
        @Override
        public T doInStatement(Statement stmt) throws SQLException {
            ResultSet rs = null;
            try {
                rs = stmt.executeQuery(sql);
                ResultSet rsToUse = rs;
                if (nativeJdbcExtractor != null) {
                    rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
                }
                return rse.extractData(rsToUse);
            }
            finally {
                JdbcUtils.closeResultSet(rs);
            }
        }
        @Override
        public String getSql() {
            return sql;
        }
    }

    return execute(new QueryStatementCallback());
}

我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。

  • 17
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值