zl程序教程

您现在的位置是:首页 >  其他

当前栏目

浅谈设计模式-模板方法模式

2023-09-27 14:29:07 时间

书接上回,本篇讲一下行为型模式-模板方法模式

模板方法模式

定义:定义一个算法的骨架,并允许子类为一个或多个步骤提供实现

作用:模板方法使得子类可以在不改变算法结构的情况下,重写定义算法的某些步骤

代码模板

/**
 * 模板父类
 */
public abstract  class AbstractTemplate {
    /**
     * 固定算法骨架
     * 固定,不能变动,子类无法重写
     */
    public final void  templateMethod(){
        //步骤1
        this.operation1();
        //步骤2
        this.operation2();
        //步骤3
        this.operation3();
        //步骤4
        this.operation4();
    }

    private void operation1() {
        System.out.println("步骤1:一种情况:逻辑固定,子类无法继承与重写,使用private修饰");
    }

    protected final void operation2() {
        System.out.println("步骤2:一种情况:逻辑固定,允许子类继承,但不允许重写,使用protected + final修饰");
    }

    protected  void commOperation() {
        System.out.println("普通方法,不是具体的算法步骤,允许子类继承与重写,使用protected修饰");
    }


    //步骤3:一种情况:父类无法确定如何实现,由子类继承并重写,使用protected修饰
    protected abstract void operation3();
    
    protected  void operation4(){
        System.out.println("步骤4:钩子方法,父类提供默认实现,子类可选择性实现,使用protected修饰");
    }

}

方法解析

标准的模板模式中,包含几种标准的方法操作

模板方法:templateMethod,定制算法的骨架

具体操作:实现模板算法的某个具体步骤,一般是一种固定实现,不需要怎么变化,所以使用final修饰。此时考虑是否需要子类访问,如果不需要使用private修饰,如果需要使用protected修饰。operation1  operation2 方法就是具体操作。

通用操作:模板类中定义的允许子类自由访问的方法,但不属于算法步骤,而是一些辅助方法。commOperation 方法就是通用的操作。

原语操作:算法步骤,模板方法必须调用的操作,而且父类中无法确定如何实现,需要子类具体实现。operation3 方法就是通用的操作。

钩子操作:算法步骤,但不是核心算法步骤,父类有默认实现,子类可以根据自己情况选择重写或者沿用,一般认为是算法的拓展点,非必须操作。operation4 方法就是通用的操作。

结语:标准的模板模式包含上面的诸多操作,但真实开发中,有模板方法,原语操作,钩子操作就算模板方法了。

案例分析

需求:设计一套工具类,实现统计算法耗时功能

逻辑接口

/**
 * 逻辑任务,该doJob逻辑需要统计耗时
 */
@FunctionalInterface
public interface TaskJob {
    void doJob();
}

模板类

/**
* 模板类
*/
public  abstract  class LogicStatisTime {
    //模板方法,固定统计时间算法
    public  final void excute(TaskJob taskJob){
        long begin = this.begin();
        taskJob.doJob();
        long end = this.end();
        this.statis(begin, end);
        this.endStatis();
    }
    //开始计时,算法步骤,固定
    private Long begin(){
        long begin = System.currentTimeMillis();
        System.out.println("计时开始T1:" + begin);
        return begin;
    }
    //结算计时,算法步骤,固定
    private Long end(){
        long end = System.currentTimeMillis();
        System.out.println("计时结束T2:" + end);
        return end;
    }
    //算法时间统计,算法步骤,由子类
    protected  abstract void statis(Long begin, Long end);

    //算法步骤,父类默认实现,子类可以选择性拓展
    protected void endStatis() {
        System.out.println("欢迎关注:下岗码农大飞");
    }
}

统计子类-简单统计

public class SimpleLogicStatisTime extends LogicStatisTime{

    @Override
    protected void statis(Long begin, Long end) {
        System.out.println("算法耗时:" + Math.abs((begin - end)/1000 ) + "秒");
    }

    @Override
    protected void endStatis() {
        System.out.println("不要关注:下岗码农大飞");
    }
}

统计子类-漂亮统计

public class PrettyLogicStatisTime extends LogicStatisTime{

    @Override
    protected void statis(Long begin, Long end) {
        long total  = Math.abs((begin - end) );

        long hour = (total % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60);

        long min = (total % (1000 * 60 * 60)) / (1000 * 60);

        long sec = (total % (1000 * 60)) / 1000;

        if(hour > 0){
            System.out.println(hour + "时" + min + "分" + sec + "秒");
        }else if (min > 0){
            System.out.println(min + "分" + sec + "秒");
        }else if(sec > 0){
            System.out.println(sec + "秒");
        }else{
            System.out.println("耗时太少,无法统计");
        }
    }
}

测试

public class App {

    //要统计耗时的代码
    public static void doLoop(){
        for (int i = 0; i < 10; i++) {
            System.out.println("hello world....");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
       new SimpleLogicStatisTime().excute(App::doLoop);
       System.out.println("-------------------");
       new PrettyLogicStatisTime().excute(App::doLoop);
    }
}

解析

都在代码了啦,看看就可以。

适用场景

一次性实现一个算法的不变的部分,并将可变的行为留个子类来实现

各子类中的公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复

优缺点

优点
提高复用性,
提高扩展性

缺点
类数目增加
增加了系统实现的复杂度
继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要该一遍

开发案例

java web 中HttpServlet类

public abstract class HttpServlet extends GenericServlet {
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

}

其中的service方法就是模板方法,里面的doXxx方法都是需要子类重写的逻辑方法。

Mybatis中的BaseExecutor 基本执行器

 BaseExecutor是模板类,里面很多模板方法

public abstract class BaseExecutor implements Executor {

 //模板方法....
      @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }


  //交给子类实现的算法步骤

  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

}

 总结

定义一个算法的骨架,并允许子类为一个或多个步骤提供实现,但也会引起代码结构复杂,类结构膨胀,实际使用中,需要综合比较。