定义

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

结构

主要角色:

  1. 抽象类/抽象模板(Abstract Class)
  2. 具体子类/具体实现(Concrete Class)

模板模式(Template Method Pattern) - 图1
其中,abstractMethod1和abstractMethod2又称为扩展点。

应用

InputStream

  1. public abstract class InputStream implements Closeable {
  2. //...省略其他代码...
  3. public int read(byte b[], int off, int len) throws IOException {
  4. if (b == null) {
  5. throw new NullPointerException();
  6. } else if (off < 0 || len < 0 || len > b.length - off) {
  7. throw new IndexOutOfBoundsException();
  8. } else if (len == 0) {
  9. return 0;
  10. }
  11. int c = read();
  12. if (c == -1) {
  13. return -1;
  14. }
  15. b[off] = (byte)c;
  16. int i = 1;
  17. try {
  18. for (; i < len ; i++) {
  19. c = read();
  20. if (c == -1) {
  21. break;
  22. }
  23. b[off + i] = (byte)c;
  24. }
  25. } catch (IOException ee) {
  26. }
  27. return i;
  28. }
  29. public abstract int read() throws IOException;
  30. }
  31. public class ByteArrayInputStream extends InputStream {
  32. //...省略其他代码...
  33. @Override
  34. public synchronized int read() {
  35. return (pos < count) ? (buf[pos++] & 0xff) : -1;
  36. }
  37. }

AbstractList

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

Servlet

  1. public class HelloServlet extends HttpServlet {
  2. @Override
  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  4. this.doPost(req, resp);
  5. }
  6. @Override
  7. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  8. resp.getWriter().write("Hello World.");
  9. }
  10. }

JUnit

  1. public abstract class TestCase extends Assert implements Test {
  2. public void runBare() throws Throwable {
  3. Throwable exception = null;
  4. setUp();
  5. try {
  6. runTest();
  7. } catch (Throwable running) {
  8. exception = running;
  9. } finally {
  10. try {
  11. tearDown();
  12. } catch (Throwable tearingDown) {
  13. if (exception == null) exception = tearingDown;
  14. }
  15. }
  16. if (exception != null) throw exception;
  17. }
  18. /**
  19. * Sets up the fixture, for example, open a network connection.
  20. * This method is called before a test is executed.
  21. */
  22. protected void setUp() throws Exception {
  23. }
  24. /**
  25. * Tears down the fixture, for example, close a network connection.
  26. * This method is called after a test is executed.
  27. */
  28. protected void tearDown() throws Exception {
  29. }
  30. }

优点

  1. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 它在父类中提取了公共的部分代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

    缺点

  4. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。

  5. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  6. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

    扩展

    模板方法VS回调

    回调类似于函数式编程,将事先定义好的方法传入执行函数中,在合适的时机运行,根据运行时机不同可分为同步回调和异步回调。同步回调指在函数返回之前执行回调函数;异步回调指的是在函数返回之后执行回调函数。回调的例子有JdbcTemplate的RowMapper,以及前端的AJAX、Promise等。
  • 回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系。
  • 模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。

因为组合优于继承,所以回调更加灵活更易扩展。