模板模式

童話述說我的結局發表於2021-05-09

一、定義

在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行。這種型別的設計模式屬於行為型模式。模板方法模式實際上是封裝一個固定流程,該流程由幾個步驟組成,具體步驟可以由子類進行不同實現,從而讓固定的流程產生不同的結果,它非常簡單,其實就是類的繼承機制,但它卻是一個應用非常廣泛的模式,模板方法模式的本質是抽象封裝流程,具體進行實現。

模版方法模式中有兩個主要的角色

  • 抽象的模版方法 AbstractTemplate
  1. 定義了一個或多個抽象操作,以便讓子類實現。這些抽象操作叫做基本操作,它們是一個頂級邏輯的組成步驟。
  2. 定義並實現了一個模板方法。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類實現。頂級邏輯也有可能呼叫一些具體方法。
  • 具體的模版方法 ConcreteTemplate
  1. 實現父類所定義的一個或多個抽象方法,它們是一個頂級邏輯的組成步驟。
  2. 每一個抽象模板角色都可以有任意多個具體模板角色與之對應,而每一個具體模板角色都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現,從而使得頂級邏輯的實現各不相同。
 

 

 

 

 

 二、模板模式的案例

//抽象的模版類
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


 

相關文章