一、定義
在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行。這種型別的設計模式屬於行為型模式。模板方法模式實際上是封裝一個固定流程,該流程由幾個步驟組成,具體步驟可以由子類進行不同實現,從而讓固定的流程產生不同的結果,它非常簡單,其實就是類的繼承機制,但它卻是一個應用非常廣泛的模式,模板方法模式的本質是抽象封裝流程,具體進行實現。
模版方法模式中有兩個主要的角色
- 抽象的模版方法 AbstractTemplate
- 定義了一個或多個抽象操作,以便讓子類實現。這些抽象操作叫做基本操作,它們是一個頂級邏輯的組成步驟。
- 定義並實現了一個模板方法。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類實現。頂級邏輯也有可能呼叫一些具體方法。
- 具體的模版方法 ConcreteTemplate
- 實現父類所定義的一個或多個抽象方法,它們是一個頂級邏輯的組成步驟。
- 每一個抽象模板角色都可以有任意多個具體模板角色與之對應,而每一個具體模板角色都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現,從而使得頂級邏輯的實現各不相同。
二、模板模式的案例
//抽象的模版類 public abstract class AbstractTemplate { /** * 模版方法:頂層的統一實現邏輯,子類不能更改或者替換 */ public void templateMethod() { System.out.println("頂層模版類的模版方法"); abstractMethod(); hoodMethod(); concreteMethod(); } /** * 抽象方法:子類必須實現和替換的方法 */ public abstract void abstractMethod(); /** * 鉤子方法:子類可以選擇性的實現或者不實現的方法 */ public void hoodMethod() { } /** * 基本方法:頂層模版類實現的方法,子類不能更改的方法 */ public void concreteMethod() { System.out.println("頂層模版類實現的具體方法"); } }
//具體的模版類 public class ConcreteTemplate extends AbstractTemplate{ @Override public void abstractMethod() { System.out.println("子類實現的抽象方法"); } @Override public void hoodMethod() { // TODO Auto-generated method stub System.out.println("子類更改的鉤子方法"); } }
public class TemplatePatternMain { public static void main(String[] args) { AbstractTemplate template = new ConcreteTemplate(); template.templateMethod(); } }
三、模板模式在原始碼中的應用
下面程式碼是JDK 中的 AbstractList原始碼部分
package java.util; public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { protected transient int modCount = 0; protected AbstractList() { } public boolean add(E var1) { this.add(this.size(), var1); return true; } public abstract E get(int var1);
從上面原始碼可以看到 get()是一個抽象方法,那麼它的邏輯就是交給子類來實現,其實我們天天在用的ArrayList 就是 AbstractList 的子類。同理,有 AbstractList 就有 AbstractSet 和AbstractMap, 不信的朋友可以去看下對應的原始碼。現在上來都是封裝的架構,現在的開發人員可能用的慢慢少了,以前很多程式設計師天天在用的HttpServlet,有三個方法 service()和 doGet()、doPost()方法,都是模板方法的抽象實現。看原始碼部分可以發現,其實在碼源中這三個方法其實也沒做啥子事,我們大都是覆蓋了他原方法自己寫處理邏輯。這個場景其實也可以叫成模板模式
public abstract class HttpServlet extends GenericServlet { private static final String METHOD_DELETE = "DELETE"; private static final String METHOD_HEAD = "HEAD"; private static final String METHOD_GET = "GET"; private static final String METHOD_OPTIONS = "OPTIONS"; private static final String METHOD_POST = "POST"; private static final String METHOD_PUT = "PUT"; private static final String METHOD_TRACE = "TRACE"; private static final String HEADER_IFMODSINCE = "If-Modified-Since"; private static final String HEADER_LASTMOD = "Last-Modified"; private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings"); public HttpServlet() { } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(405, msg); } else { resp.sendError(400, msg); } } protected long getLastModified(HttpServletRequest req) { return -1L; } protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { NoBodyResponse response = new NoBodyResponse(resp); this.doGet(req, response); response.setContentLength(); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(405, msg); } else { resp.sendError(400, msg); } } protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_put_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(405, msg); } else { resp.sendError(400, msg); } }
今年來吃的沒事幹自己在學JDBC及MyBatis封裝自己資料庫架構外掛,所以吃的沒事又看了一下其中的原始碼,其實在MyBatis 框架也有一些好玩的東西,來看一下其中的 BaseExecutor 類,它是一個基礎的SQL 執行類,實現了大部分的 SQL 執行邏輯,然後把幾個方法交給子類定製化完成
public abstract class BaseExecutor implements Executor { private static final Log log = LogFactory.getLog(BaseExecutor.class); protected Transaction transaction; protected Executor wrapper; protected ConcurrentLinkedQueue<BaseExecutor.DeferredLoad> deferredLoads; protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache; protected Configuration configuration; protected int queryStack = 0; private boolean closed; protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue(); this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; } public Transaction getTransaction() { if (this.closed) { throw new ExecutorException("Executor was closed."); } else { return this.transaction; } } public void close(boolean forceRollback) { try { try { this.rollback(forceRollback); } finally { if (this.transaction != null) { this.transaction.close(); } } } catch (SQLException var11) { log.warn("Unexpected exception on closing transaction. Cause: " + var11); } finally { this.transaction = null; this.deferredLoads = null; this.localCache = null; this.localOutputParameterCache = null; this.closed = true; } } public boolean isClosed() { return this.closed; } public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (this.closed) { throw new ExecutorException("Executor was closed."); } else { this.clearLocalCache(); return this.doUpdate(ms, parameter); } } public List<BatchResult> flushStatements() throws SQLException { return this.flushStatements(false); } public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException { if (this.closed) { throw new ExecutorException("Executor was closed."); } else { return this.doFlushStatements(isRollBack); } } public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql); return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (this.closed) { throw new ExecutorException("Executor was closed."); } else { if (this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; list = resultHandler == null ? (List)this.localCache.getObject(key) : null; if (list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { Iterator i$ = this.deferredLoads.iterator(); while(i$.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next(); deferredLoad.load(); } this.deferredLoads.clear(); if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } } public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); return this.doQueryCursor(ms, parameter, rowBounds, boundSql); } public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) { if (this.closed) { throw new ExecutorException("Executor was closed."); } else { BaseExecutor.DeferredLoad deferredLoad = new BaseExecutor.DeferredLoad(resultObject, property, key, this.localCache, this.configuration, targetType); if (deferredLoad.canLoad()) { deferredLoad.load(); } else { this.deferredLoads.add(new BaseExecutor.DeferredLoad(resultObject, property, key, this.localCache, this.configuration, targetType)); } } } public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (this.closed) { throw new ExecutorException("Executor was closed."); } else { CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); Iterator i$ = parameterMappings.iterator(); while(i$.hasNext()) { ParameterMapping parameterMapping = (ParameterMapping)i$.next(); if (parameterMapping.getMode() != ParameterMode.OUT) { String propertyName = parameterMapping.getProperty(); Object value; if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = this.configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } cacheKey.update(value); } } if (this.configuration.getEnvironment() != null) { cacheKey.update(this.configuration.getEnvironment().getId()); } return cacheKey; } } public boolean isCached(MappedStatement ms, CacheKey key) { return this.localCache.getObject(key) != null; } public void commit(boolean required) throws SQLException { if (this.closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } else { this.clearLocalCache(); this.flushStatements(); if (required) { this.transaction.commit(); } } } public void rollback(boolean required) throws SQLException { if (!this.closed) { try { this.clearLocalCache(); this.flushStatements(true); } finally { if (required) { this.transaction.rollback(); } } } } public void clearLocalCache() { if (!this.closed) { this.localCache.clear(); this.localOutputParameterCache.clear(); } } protected abstract int doUpdate(MappedStatement var1, Object var2) throws SQLException; protected abstract List<BatchResult> doFlushStatements(boolean var1) throws SQLException; protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException; protected abstract <E> Cursor<E> doQueryCursor(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4) throws SQLException; protected void closeStatement(Statement statement) { if (statement != null) { try { statement.close(); } catch (SQLException var3) { } } } protected void applyTransactionTimeout(Statement statement) throws SQLException { StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), this.transaction.getTimeout()); } private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) { if (ms.getStatementType() == StatementType.CALLABLE) { Object cachedParameter = this.localOutputParameterCache.getObject(key); if (cachedParameter != null && parameter != null) { MetaObject metaCachedParameter = this.configuration.newMetaObject(cachedParameter); MetaObject metaParameter = this.configuration.newMetaObject(parameter); Iterator i$ = boundSql.getParameterMappings().iterator(); while(i$.hasNext()) { ParameterMapping parameterMapping = (ParameterMapping)i$.next(); if (parameterMapping.getMode() != ParameterMode.IN) { String parameterName = parameterMapping.getProperty(); Object cachedValue = metaCachedParameter.getValue(parameterName); metaParameter.setValue(parameterName, cachedValue); } } } } } 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; } protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = this.transaction.getConnection(); return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection; } public void setExecutorWrapper(Executor wrapper) { this.wrapper = wrapper; } private static class DeferredLoad { private final MetaObject resultObject; private final String property; private final Class<?> targetType; private final CacheKey key; private final PerpetualCache localCache; private final ObjectFactory objectFactory; private final ResultExtractor resultExtractor; public DeferredLoad(MetaObject resultObject, String property, CacheKey key, PerpetualCache localCache, Configuration configuration, Class<?> targetType) { this.resultObject = resultObject; this.property = property; this.key = key; this.localCache = localCache; this.objectFactory = configuration.getObjectFactory(); this.resultExtractor = new ResultExtractor(configuration, this.objectFactory); this.targetType = targetType; } public boolean canLoad() { return this.localCache.getObject(this.key) != null && this.localCache.getObject(this.key) != ExecutionPlaceholder.EXECUTION_PLACEHOLDER; } public void load() { List<Object> list = (List)this.localCache.getObject(this.key); Object value = this.resultExtractor.extractObjectFromList(list, this.targetType); this.resultObject.setValue(this.property, value); } } }
上面的原始碼中如 doUpdate、doFlushStatements、doQuery、doQueryCursor 這幾個方法就是交給了它的子類去實現了
從上面類圖可以看到他有四個子類,隨便找兩個子類的實現對比下
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; int var6; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null); stmt = this.prepareStatement(handler, ms.getStatementLog()); var6 = handler.update(stmt); } finally { this.closeStatement(stmt); } return var6; }
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null); BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); Statement stmt; if (sql.equals(this.currentSql) && ms.equals(this.currentStatement)) { int last = this.statementList.size() - 1; stmt = (Statement)this.statementList.get(last); this.applyTransactionTimeout(stmt); handler.parameterize(stmt); BatchResult batchResult = (BatchResult)this.batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { Connection connection = this.getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, this.transaction.getTimeout()); handler.parameterize(stmt); this.currentSql = sql; this.currentStatement = ms; this.statementList.add(stmt); this.batchResultList.add(new BatchResult(ms, sql, parameterObject)); } handler.batch(stmt); return -2147482646; }
從上面可以看到SimpleExecutor是一種常規執行器,每次執行都會建立一個statement,用完後關閉,而BatchExecutor是批處理型執行器,doUpdate預處理儲存過程或批處理操作
四、總結
優點:
1、利用模板方法將相同處理邏輯的程式碼放到抽象父類中,可以提高程式碼的複用性。
2、將不同的程式碼不同的子類中,通過對子類的擴充套件增加新的行為,提高程式碼的擴充套件性。
3、把不變的行為寫在父類上,去除子類的重複程式碼,提供了一個很好的程式碼複用平臺,符合開閉原則。
缺點:
1、類數目的增加,每一個抽象類都需要一個子類來實現,這樣導致類的個數增加。
2、類數量的增加,間接地增加了系統實現的複雜度。
3、繼承關係自身缺點,如果父類新增新的抽象方法,所有子類都要改一遍。
補充:這種玩意其實在演算法場景中應用的比較多,或者是在共性多的場景也可以這麼玩
git:原始碼:https://github.com/ljx958720/design_patterns.git