模板模式
Posted xiaojiesir
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板模式相关的知识,希望对你有一定的参考价值。
模板模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
介绍
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
何时使用:有一些通用的方法。
如何解决:将这些通用算法抽象出来。
关键代码:在抽象类实现,其他步骤在子类实现。
应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
角色
AbstractClass(抽象类):
在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实
现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
ConcreteClass(具体子类):
它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。这个模板方法定义在抽象类中,并由子类不加以修改地完全继承下来。模板方法是一个具体方法,它给
出了一个顶层逻辑框架,而逻辑的组成步骤在抽象类中可以是具体方法,也可以是抽象方法。
基本方法是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。
- 抽象方法:一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法:可以与一些具体步骤 “挂钩” ,以实现在不同条件下执行模板方法中的不同步骤
示例
抽象类定义如下:
1 public abstract class ACourse 2 3 protected final void makeCourse() 4 this.makePPT(); 5 this.makeVideo(); 6 if (needWriteArticle()) 7 this.writeArticle(); 8 9 this.packageCourse(); 10 11 12 final void makePPT() 13 System.out.println("1. 制作PPT"); 14 15 16 final void makeVideo() 17 System.out.println("2. 制作视频"); 18 19 20 final void writeArticle() 21 System.out.println("3. 编写课程笔记"); 22 23 24 //钩子方法 25 protected boolean needWriteArticle() 26 return false; 27 28 29 abstract void packageCourse(); 30
其中的 makeCourse 方法是模板方法,它定义了制作网课的基本流程,makePPT、makeVideo、writeArticle 这三个步骤在所有课程中都是固定的,所以用 final 关键字修饰;packageCourse 方法在所有课程
中都可能不一样,所以声明为抽象方法,由子类自行实现;钩子方法 needWriteArticle 返回一个 boolean 类型的值,控制是否编写课程笔记
子类 JavaCourse,实现了抽象方法 packageCourse
,重写了钩子方法 needWriteArticle
1 public class JavaCourse extends ACourse 2 @Override 3 void packageCourse() 4 System.out.println("4. 提供Java课程源代码"); 5 6 7 @Override 8 protected boolean needWriteArticle() 9 return true; 10 11
子类 FECourse,实现了抽象方法 packageCourse
,重写了钩子方法 needWriteArticle
,其中把钩子方法的结果交给客户端确定
1 public class FECourse extends ACourse 2 private boolean needWriteArticleFlag = false; 3 @Override 4 void packageCourse() 5 System.out.println("4.1 提供课程的前端代码"); 6 System.out.println("4.2 提供课程的图片等多媒体素材"); 7 8 9 public FECourse(boolean needWriteArticleFlag) 10 this.needWriteArticleFlag = needWriteArticleFlag; 11 12 13 @Override 14 protected boolean needWriteArticle() 15 return this.needWriteArticleFlag; 16 17
客户端测试
public class Test public static void main(String[] args) System.out.println("Java课程start---"); ACourse javaCourse = new JavaCourse(); javaCourse.makeCourse(); System.out.println("Java课程end---\n"); System.out.println("前端课程start---"); ACourse feCourse = new FECourse(false); feCourse.makeCourse(); System.out.println("前端课程end---");
结果
Java课程start--- 1. 制作PPT 2. 制作视频 3. 编写笔记 4. 提供Java课程源代码 Java课程end--- 前端课程start--- 1. 制作PPT 2. 制作视频 4.1 提供课程的前端代码 4.2 提供课程的图片等多媒体素材 前端课程end---
模板方法模式总结
模板方法模式的主要优点如下:
- 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
- 模板方法模式是一种代码复用技术,它在类库设计中尤为重要,它提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为,它鼓励我们恰当使用继承来实现代码复用。
- 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行。
- 在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则。
模板方法模式的主要缺点如下:
- 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象,此时,可结合桥接模式来进行设计。
适用场景:
- 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现。即:一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
源码分析模板方法模式的典型应用
Servlet 中的模板方法模式
Servlet(Server Applet)是Java Servlet的简称,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。在每一个 Servlet 都必须要实现 Servlet 接口,GenericServlet 是一
个通用的、不特定于任何协议的Servlet,它实现了 Servlet 接口,而 HttpServlet 继承于 GenericServlet,实现了 Servlet 接口,为 Servlet 接口提供了处理HTTP协议的通用实现,所以我们定义的 Servlet 只
需要继承 HttpServlet 即可。
HttpServlet
的简要代码如下所示
1 public abstract class HttpServlet extends GenericServlet 2 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 3 // ... 4 5 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 6 // ... 7 8 protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 9 // ... 10 11 protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 12 // ... 13 14 protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 15 // ... 16 17 protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 18 // ... 19 20 protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 21 // ... 22 23 24 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 25 String method = req.getMethod(); 26 27 if (method.equals(METHOD_GET)) 28 long lastModified = getLastModified(req); 29 if (lastModified == -1) 30 // servlet doesn‘t support if-modified-since, no reason 31 // to go through further expensive logic 32 doGet(req, resp); 33 else 34 long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); 35 if (ifModifiedSince < lastModified) 36 // If the servlet mod time is later, call doGet() 37 // Round down to the nearest second for a proper compare 38 // A ifModifiedSince of -1 will always be less 39 maybeSetLastModified(resp, lastModified); 40 doGet(req, resp); 41 else 42 resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 43 44 45 46 else if (method.equals(METHOD_HEAD)) 47 long lastModified = getLastModified(req); 48 maybeSetLastModified(resp, lastModified); 49 doHead(req, resp); 50 51 else if (method.equals(METHOD_POST)) 52 doPost(req, resp); 53 54 else if (method.equals(METHOD_PUT)) 55 doPut(req, resp); 56 57 else if (method.equals(METHOD_DELETE)) 58 doDelete(req, resp); 59 60 else if (method.equals(METHOD_OPTIONS)) 61 doOptions(req,resp); 62 63 else if (method.equals(METHOD_TRACE)) 64 doTrace(req,resp); 65 66 else 67 // 68 // Note that this means NO servlet supports whatever 69 // method was requested, anywhere on this server. 70 // 71 72 String errMsg = lStrings.getString("http.method_not_implemented"); 73 Object[] errArgs = new Object[1]; 74 errArgs[0] = method; 75 errMsg = MessageFormat.format(errMsg, errArgs); 76 77 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); 78 79 80 81 // ...省略... 82
在 HttpServlet
的 service
方法中,首先获得到请求的方法名,然后根据方法名调用对应的 doXXX
方法,比如说请求方法为GET,那么就去调用 doGet
方法;请求方法为POST,那么就去调用 doPost
方法
HttpServlet 相当于定义了一套处理 HTTP 请求的模板;service 方法为模板方法,定义了处理HTTP请求的基本流程;doXXX 等方法为基本方法,根据请求方法做相应的处理,子类可重写这些方法;
HttpServletRequest 中的Method则起到钩子方法的作用.
在开发javaWeb应用时,自定义的Servlet类一般都扩展 HttpServlet
类,譬如我们实现一个输出 Hello World!
的 Servlet
如下
1 import java.io.*; 2 import javax.servlet.*; 3 import javax.servlet.http.*; 4 5 // 扩展 HttpServlet 类 6 public class HelloWorld extends HttpServlet 7 8 public void init() throws ServletException 9 // ... 10 11 12 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 13 response.setContentType("text/html"); 14 PrintWriter out = response.getWriter(); 15 out.println("<h1>Hello World!</h1>"); 16 17 18 public void destroy() 19 // ... 20 21
该自定义的 Servlet
重写了 doGet
方法,当客户端发起 GET 请求时将得到 <h1>Hello World!</h1>
。
Mybatis BaseExecutor接口中的模板方法模式
Executor
是 Mybatis 的核心接口之一,其中定义了数据库操作的基本方法,该接口的代码如下:
1 public interface Executor 2 3 ResultHandler NO_RESULT_HANDLER = null; 4 // 执行 update、insert、delete 三种类型的SQL语句 5 int update(MappedStatement ms, Object parameter) throws SQLException; 6 // 执行selete类型的SQL语句,返回值分为结果对象列表或游标对象 7 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; 8 / 9 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; 10 11 <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; 12 // 批量执行SQL语句 13 List<BatchResult> flushStatements() throws SQLException; 14 // 提交事务 15 void commit(boolean required) throws SQLException; 16 // 回滚事务 17 void rollback(boolean required) throws SQLException; 18 // 创建缓存中用到的CacheKey对象 19 CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); 20 // 根据CacheKey对象查找缓存 21 boolean isCached(MappedStatement ms, CacheKey key); 22 // 清空一级缓存 23 void clearLocalCache(); 24 // 延迟加载一级缓存中的数据 25 void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType); 26 // 获取事务对象 27 Transaction getTransaction(); 28 // 关闭Executor对象 29 void close(boolean forceRollback); 30 // 检测Executor是否已关闭 31 boolean isClosed(); 32 33 void setExecutorWrapper(Executor executor); 34 35
BaseExecutor 中主要提供了缓存管理和事务管理的基本功能,继承 BaseExecutor 的子类只需要实现四个基本方法来完成数据库的相关操作即可,这四个方法分别是:doUpdate() 方法、doQuery() 方法、
doQueryCursor() 方法、doFlushStatement() 方法,其余功能都在 BaseExecutor 中实现。
BaseExecutor的部分代码如下,其中的 query() 方法首先会创建 CacheKey 对象,并根据 CacheKey 对象查找一级缓存,如果缓存命中则返回缓存中记录的结果对象,如果未命中则查询数据库得到结果集,
之后将结果集映射成结果对象并保存到一级缓存中,同时返回结果对象。
1 public abstract class BaseExecutor implements Executor 2 protected Transaction transaction; 3 protected Executor wrapper; 4 5 protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; 6 protected PerpetualCache localCache; 7 protected PerpetualCache localOutputParameterCache; 8 protected Configuration configuration; 9 10 protected int queryStack = 0; 11 private boolean closed; 12 13 @Override 14 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException 15 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 16 if (closed) 17 throw new ExecutorException("Executor was closed."); 18 19 if (queryStack == 0 && ms.isFlushCacheRequired()) 20 clearLocalCache(); 21 22 List<E> list; 23 try 24 queryStack++; 25 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 26 if (list != null) 27 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 28 else 29 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 30 31 finally 32 queryStack--; 33 34 if (queryStack == 0) 35 for (DeferredLoad deferredLoad : deferredLoads) 36 deferredLoad.load(); 37 38 // issue #601 39 deferredLoads.clear(); 40 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) 41 // issue #482 42 clearLocalCache(); 43 44 45 return list; 46 47 48 protected abstract int doUpdate(MappedStatement ms, Object parameter) 49 throws SQLException; 50 51 protected abstract List<BatchResult> doFlushStatements(boolean isRollback) 52 throws SQLException; 53 54 protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) 55 throws SQLException; 56 57 protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) 58 throws SQLException; 59 60 // 省略.... 61
BaseExecutor 的子类有四个分别是 SimpleExecotor、ReuseExecutor、BatchExecutor、ClosedExecutor,由于这里使用了模板方法模式,一级缓存等固定不变的操作都封装到了 BaseExecutor 中,因此子
类就不必再关心一级缓存等操作,只需要专注实现4个基本方法的实现即可。
这里对这四个子类的功能做一个简要的介绍:
SimpleExecutor
是Mybatis执行Mapper语句时默认使用的Executor
,提供最基本的Mapper语句执行功能,没有过多的封装的ReuseExecutor
提供了Statement
重用的功能,通过statementMap
字段缓存使用过的Statement
对象进行重用,可以减少SQL预编译以及创建和销毁Statement
对象的开销,从而提高性能BatchExecutor
实现了批处理多条SQL语句的功能,在客户端缓存多条SQL并在合适的时机将多条SQL打包发送给数据库执行,从而减少网络方面的开销,提升系统的性能ClosedExecutor
只是某个类的一个内部类
以上是关于模板模式的主要内容,如果未能解决你的问题,请参考以下文章