模版方法模式
模版方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。——《Head First 设计模式(中文版)》
简单例子 冲泡咖啡和茶
咖啡冲泡步骤:
把水煮沸
用沸水冲泡咖啡
把咖啡倒进杯子
加糖和牛奶
茶冲泡步骤:
把水煮沸
用沸水浸泡茶叶
把咖啡倒进杯子
加柠檬
把共有的步骤及其顺序抽象为抽象类中的模版方法,可以共享的步骤放在抽象类中作为普通方法,需要子类实现的作为抽象方法。
抽象类 CaffeineBerverage 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class CaffeineBeverage { public final void prepareRecipe () { boilWater(); brew(); pourInCup(); addCondiments(); } public abstract void brew () ; public abstract void addCondiments () ; public void boilWater () { System.out.println("Boiling water" ); } public void pourInCup () { System.out.println("Pouring into cup" ); } }
咖啡实现类 1 2 3 4 5 6 7 8 9 10 11 12 public class Coffee extends CaffeineBeverage { @Override public void brew () { System.out.println("Steeping the coffee" ); } @Override public void addCondiments () { System.out.println("Adding Sugar and Milk" ); } }
茶实现类 1 2 3 4 5 6 7 8 9 10 11 12 public class Tea extends CaffeineBeverage { @Override public void brew () { System.out.println("Steeping the tea" ); } @Override public void addCondiments () { System.out.println("Adding Lemon" ); } }
测试类 1 2 3 4 5 6 7 8 9 10 public class Test { public static void main (String[] args) { CaffeineBeverage coffee = new Coffee(); coffee.prepareRecipe(); System.out.println("--------------------" ); CaffeineBeverage tea = new Tea(); tea.prepareRecipe(); } }
1 2 3 4 5 6 7 8 9 Boiling water Steeping the cffee Pouring into cup Adding Sugar and Milk -------------------- Boiling water Steeping the tea Pouring into cup Adding Lemon
Java API 中的例子 java.io.InputStream
的 read()
方法,是抽象方法,需要由子类实现,而这个方法又会被 read(byte b[], int off, int len)
模版方法使用。
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 package java.io;public abstract class InputStream implements Closeable { public abstract int read () throws IOException ; public int read (byte b[], int off, int len) throws IOException { if (b == null ) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0 ) { return 0 ; } int c = read(); if (c == -1 ) { return -1 ; } b[off] = (byte )c; int i = 1 ; try { for (; i < len ; i++) { c = read(); if (c == -1 ) { break ; } b[off + i] = (byte )c; } } catch (IOException ee) { } return i; } }
1 2 3 4 5 6 7 8 9 package java.io;public class FileInputStream extends InputStream { public int read () throws IOException { return read0(); } private native int read0 () throws IOException ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package java.io;public class ObjectInputStream extends InputStream implements ObjectInput , ObjectStreamConstants { private final BlockDataInputStream bin; public int read () throws IOException { return bin.read(); } private class BlockDataInputStream extends InputStream implements DataInput { public int read () throws IOException { if (blkmode) { if (pos == end) { refill(); } return (end >= 0 ) ? (buf[pos++] & 0xFF ) : -1 ; } else { return in.read(); } } } }
Java EE API 中的例子
Servlet入门- 廖雪峰的官方网站
WEB 开发中常用 HttpServlet
里面的 service
方法处理请求,service
方法里面定义了调用流程,根据客户端调用的不同方式调用不同的方法,比如 doGet
,doPost
,doDelete
等实现 restful 调用,具体的 doGet
,doPost
方法实现可以在自定义的 Servlet
中进行重写。
但需要注意的是 doGet
,doPost
,doDelete
等并没有被定义为抽象方法。
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 41 package javax.servlet.http;public abstract class HttpServlet extends GenericServlet { protected void service (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET" )) { lastModified = this .getLastModified(req); if (lastModified == -1L ) { this .doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader("If-Modified-Since" ); if (ifModifiedSince < lastModified) { this .maybeSetLastModified(resp, lastModified); this .doGet(req, resp); } else { resp.setStatus(304 ); } } } else if (method.equals("HEAD" )) { lastModified = this .getLastModified(req); this .maybeSetLastModified(resp, lastModified); this .doHead(req, resp); } else if (method.equals("POST" )) { this .doPost(req, resp); } else if (method.equals("PUT" )) { this .doPut(req, resp); } else if (method.equals("DELETE" )) { this .doDelete(req, resp); } else if (method.equals("OPTIONS" )) { this .doOptions(req, resp); } else if (method.equals("TRACE" )) { this .doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented" ); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501 , errMsg); } } }
Mybatis 中的例子 Mybatis 框架中的 BaseExecutor 类是一个基础的 SQL 执行类,实现了大部分 SQL 的执行逻辑,然后把几个方法交给子类定制化完成,例如模版方法 queryFromDatabase
中调用的 doQuery
方法就是一个抽象方法,需要子类提供实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package org.apache.ibatis.executor;public abstract class BaseExecutor implements Executor { protected abstract <E> List<E> doQuery (MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException ; private <E> List<E> queryFromDatabase (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { this .localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER); List list; try { list = this .doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { this .localCache.removeObject(key); } this .localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { this .localOutputParameterCache.putObject(key, parameter); } return list; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package org.apache.ibatis.executor;public class SimpleExecutor extends BaseExecutor { public <E> List<E> doQuery (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null ; List var9; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this .wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = this .prepareStatement(handler, ms.getStatementLog()); var9 = handler.query(stmt, resultHandler); } finally { this .closeStatement(stmt); } return var9; } }
优点
将相同处理逻辑的代码放到抽象父类中,把不变的行为写在父类中,去除了子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
将不同的逻辑放到不同的子类中,通过子类的扩展增加新的行为,提高了代码的扩展性。
通过父类调用子类的操作,通过对子类的扩展增加新的行为,实现了反向控制。
缺点
每个抽象类都需要至少一个子类来实现,导致了类数量的增加。
类数量增加间接增加了系统的复杂性。
因为继承关系的自身缺点,如果父类添加一个新的抽象方法,所有子类都要实现一遍。