# 模板方法模式TemplateMethod

loading

# 一、概念

# 1、定义

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

# 2、定义补充

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

# 3、类型

行为型

# 4、适用场景

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复。

# 5、优点

  • 提高复用性
  • 提高扩展性
  • 符合开闭原则

# 6、缺点

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

# 7、扩展

  • 钩子方法

是模板对子类更进一步的开放和扩展。

# 8、相关设计模式

  • 模板方法和工厂方法模式

工厂方法模式是对模板方法的特殊实现

  • 模板方法和策略模式

都有封装算法。策略模式目的是使不同的算法可以相互替换,并且不影响应用层的使用,可以改变算法的流程;而模板方法是针对定义一个算法的流程,将一些不太一样的具体实现步骤交给子类实现,不改变算法的流程。

# 二、应用

首先创建一个课程的抽象类:

public abstract class ACourse {

    /**
     * 制作课程
     */
    protected final void makeCourse() {
        makePPT();
        makeVideo();
        if (needWriteArticle()) {
            writeArticle();
        }
        packageCourse();
    }

    final void makePPT() {
        System.out.println("制作PPT");
    }

    final void makeVideo() {
        System.out.println("制作视频");
    }

    final void writeArticle() {
        System.out.println("编写手记");
    }

    /**
     * writeArticle() 的钩子方法,子类可以覆盖
     *
     * @return
     */
    protected boolean needWriteArticle() {
        return false;
    }

    /**
     * 包装课程
     */
    abstract void packageCourse();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

在制作课程中,制作PPT、制作视频、编写手记的过程都是固定的,所以定义成了 final 类型,总的制作流程 makeCourse() 也是固定的,所以用 final 修饰。而是否需要编写手记每个课程不一样,所以有一个 needWriteArticle() 来控制,最后包装课程的 packageCourse() 方法定义成抽象的,交给子类实现。

创建一个后端设计模式的课程:

public class DesignPatternCourse extends ACourse {

    @Override
    void packageCourse() {
        System.out.println("提供课程Java源代码");
    }
}
1
2
3
4
5
6
7

一个前端的课程:

public class FrontEndCourse extends ACourse {

    @Override
    void packageCourse() {
        System.out.println("提供课程前端源代码");
        System.out.println("提供课程内部的多媒体素材");
    }
}
1
2
3
4
5
6
7
8

测试:

public class Test {

    public static void main(String[] args) {
        System.out.println("设计模式课程 start ");
        ACourse designPatternCourse = new DesignPatternCourse();
        designPatternCourse.makeCourse();
        System.out.println("设计模式课程 end ");

        System.out.println("前端课程 start ");
        ACourse frontEndCourse = new FrontEndCourse();
        frontEndCourse.makeCourse();
        System.out.println("前端课程 end ");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

执行结果:

设计模式课程 start 
制作PPT
制作视频
提供课程Java源代码
设计模式课程 end 
前端课程 start 
制作PPT
制作视频
提供课程前端源代码
提供课程内部的多媒体素材
前端课程 end 
1
2
3
4
5
6
7
8
9
10
11

现在设计模式课程需要写手记,所以修改 DesignPatternCourse 类,复写父类的 needWriteArticle() 方法:

public class DesignPatternCourse extends ACourse {

    @Override
    void packageCourse() {
        System.out.println("提供课程Java源代码");
    }

    @Override
    protected boolean needWriteArticle() {
        return true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

重新执行测试:

设计模式课程 start 
制作PPT
制作视频
编写手记
提供课程Java源代码
设计模式课程 end 
前端课程 start 
制作PPT
制作视频
提供课程前端源代码
提供课程内部的多媒体素材
前端课程 end 
1
2
3
4
5
6
7
8
9
10
11
12

此时,设计模式课程因为在 needWriteArticle() 方法中返回了 true,所以有了编写手记的流程,而前端课程因为没有复写 needWriteArticle() 方法仍然采用的是模板中返回 false 的实现。

此时的类图:

模板方法1

如果前端课程又细分了两个:Vue课程React课程,两个课程一个需要手记另一个不需要手记,此时就需要在 FrontEndCourse 类中重写 needWriteArticle() 方法,并且将具体的返回值开放给应用层:

public class FrontEndCourse extends ACourse {

    private boolean needWriteArticleFlag;

    public FrontEndCourse(boolean needWriteArticleFlag) {
        this.needWriteArticleFlag = needWriteArticleFlag;
    }

    @Override
    void packageCourse() {
        System.out.println("提供课程前端源代码");
        System.out.println("提供课程内部的多媒体素材");
    }

    @Override
    protected boolean needWriteArticle() {
        return needWriteArticleFlag;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

使用 needWriteArticleFlag 来控制是否写手记,采用构造方法的方式注入进来值。

应用层:

public class Test {

    public static void main(String[] args) {
        System.out.println("设计模式课程 start ");
        ACourse designPatternCourse = new DesignPatternCourse();
        designPatternCourse.makeCourse();
        System.out.println("设计模式课程 end ");

        System.out.println("前端课程Vue start ");
        ACourse vueCourse = new FrontEndCourse(true);
        vueCourse.makeCourse();
        System.out.println("前端课程Vue end ");

        System.out.println("前端课程React start ");
        ACourse reactCourse = new FrontEndCourse(true);
        reactCourse.makeCourse();
        System.out.println("前端课程React end ");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

执行结果:

设计模式课程 start 
制作PPT
制作视频
编写手记
提供课程Java源代码
设计模式课程 end 
前端课程Vue start 
制作PPT
制作视频
编写手记
提供课程前端源代码
提供课程内部的多媒体素材
前端课程Vue end 
前端课程React start 
制作PPT
制作视频
编写手记
提供课程前端源代码
提供课程内部的多媒体素材
前端课程React end 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

虽然 DesignPatternCourseFrontEndCourse 都是继承自 ACourse , 但实际上 DesignPatternCourse 已经到一个具体的课程了,就和 FrontEndCourse 课程中的 Vue课程React课程 一样。

ACourse 中的 makeCourse() 方法注意一定要是 final 的,因为制作课程的过程是固定好的,先如何再如何是定义好的,不允许子类打乱这个流程。

此时的类图:

模板方法2

# 三、源码中的应用

# 1、AbstractList

其中的 addAll() 方法就相当于一个定义好流程的方法,所有的子类都需要按照这个流程走:

    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);
        boolean modified = false;
        for (E e : c) {
            add(index++, e);
            modified = true;
        }
        return modified;
    }
1
2
3
4
5
6
7
8
9

这个类的 get() 是一个抽象方法,交由子类来具体实现。下面这个是 ArrayList 的实现:

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
1
2
3
4
5

类似的还有 AbstractSetAbstractMap,都是一样的原理。

# 2、HttpServlet

doPost()doGet()service() 这3个方法 HttpServlet 都有默认的实现,子类继承的时候可以重写这些方法。

# 3、MyBatis 中的 BaseExecutor

它的 doUpdate()doFlushStatements()doQuery()doQueryCursor() 这几个都是抽象方法,都是交由子类来实现的。

它一共有4个子类:SimpleExecutorBatchExecutorClosedExecutorReuseExecutor

例如 SimpleExecutordoUpdate() 的实现:

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12

BatchExecutor 类又是有它自己不同的实现:

  @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
     handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // handler.parameterize(stmt);
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

上次更新: 2020-08-21 09:02:51(10 小时前)