• 【从优秀源码中学习设计模式】--- 模板方法模式


    1. 描述

    模板方法模式一般用于有标准流程性的业务场景,总体流程不变,但流程中的每一个环节可能有所不同,用标准定义来描述就是,模板方法模式定义了一个算法的步骤,并允许子类为一个或多个步骤提供具体的实现,让子类可以在不改变算法架构的情况下,重新定义算法中的某些步骤。

    2. 标准结构

    在这里插入图片描述

    public abstract class AbstractClass {
    
        public final void templateMethod(){
            method1();
            method2();
            method3();
        }
    
        protected abstract void method3();
    
        protected final void method2(){
            System.out.println("AbstractClass method2");
        }
    
        protected abstract void method1();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    public class ConcreteClassA extends AbstractClass {
        @Override
        protected void method3() {
            System.out.println("ConcreteClassA method3");
        }
    
        @Override
        protected void method1() {
            System.out.println("ConcreteClassA method1");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    public class ConcreteClassB extends AbstractClass {
        @Override
        protected void method3() {
            System.out.println("ConcreteClassB method3");
        }
    
        @Override
        protected void method1() {
            System.out.println("ConcreteClassB method1");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3. 使用场景

    当业务逻存在一些需要多个步骤来完成,某些步骤是标准统一的,某些步骤根据不同的场景又有不同的处理逻辑,如果全部写在一个类中,可能那些不同场景步骤会出现各种条件判断,无论哪个场景发生修改,都会影响整个流程,这不符合开闭原则,但如果为每个场景都分别实现一套,同样对应相同的步骤就会出现重复的逻辑,又会带来大量的重复代码。

    那么,模板方法就是解决上述问题的一种非常好的模式,利用模板方法模式可以实现代码的复用和扩展,这实际上在对代码重构时就非常有帮助。

    优点

    对于不变的部分,与变的部分做到了隔离,符合开闭原则,使用业务非常容易扩展。

    对于公用逻辑易于维护。

    缺点

    由于每一个不同的实现都要一个子类来实现,会导致类的个数变得更多。

    4. 使用案例

    4.1 Spring

    AbstractApplicationContext类的refresh方法,这里就用到了模板方法模式,有一些子方法是由AbstractApplicationContext自己实现的,有一些子方法则是让不同的实现类各自实现。

    public void refresh() throws BeansException, IllegalStateException {
    		synchronized (this.startupShutdownMonitor) {
    			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
    
    			// Prepare this context for refreshing.
    			prepareRefresh();
    
    			// Tell the subclass to refresh the internal bean factory.
    			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    			// Prepare the bean factory for use in this context.
    			prepareBeanFactory(beanFactory);
    
    			try {
    				// Allows post-processing of the bean factory in context subclasses.
    				postProcessBeanFactory(beanFactory);
    
    				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
    				// Invoke factory processors registered as beans in the context.
    				invokeBeanFactoryPostProcessors(beanFactory);
    
    				// Register bean processors that intercept bean creation.
    				registerBeanPostProcessors(beanFactory);
    				beanPostProcess.end();
    
    				// Initialize message source for this context.
    				initMessageSource();
    
    				// Initialize event multicaster for this context.
    				initApplicationEventMulticaster();
    
    				// Initialize other special beans in specific context subclasses.
    				onRefresh();
    
    				// Check for listener beans and register them.
    				registerListeners();
    
    				// Instantiate all remaining (non-lazy-init) singletons.
    				finishBeanFactoryInitialization(beanFactory);
    
    				// Last step: publish corresponding event.
    				finishRefresh();
    			}
    
    			catch (BeansException ex) {
    				if (logger.isWarnEnabled()) {
    					logger.warn("Exception encountered during context initialization - " +
    							"cancelling refresh attempt: " + ex);
    				}
    
    				// Destroy already created singletons to avoid dangling resources.
    				destroyBeans();
    
    				// Reset 'active' flag.
    				cancelRefresh(ex);
    
    				// Propagate exception to caller.
    				throw ex;
    			}
    
    			finally {
    				// Reset common introspection caches in Spring's core, since we
    				// might not ever need metadata for singleton beans anymore...
    				resetCommonCaches();
    				contextRefresh.end();
    			}
    		}
    	}
    
    • 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
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    4.2 Spring MVC

    AbstractController提供了handleRequest方法,负责请求处理并返回ModelAndView,其也是通过模板方法模式来实现,这点作者甚至直接写在了文档说明中。

    在这里插入图片描述

    public abstract class AbstractController extends WebContentGenerator implements Controller {
    
    	@Override
    	@Nullable
    	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    		if (HttpMethod.OPTIONS.matches(request.getMethod())) {
    			response.setHeader("Allow", getAllowHeader());
    			return null;
    		}
    
    		// Delegate to WebContentGenerator for checking and preparing.
    		checkRequest(request);
    		prepareResponse(response);
    
    		// Execute handleRequestInternal in synchronized block if required.
    		if (this.synchronizeOnSession) {
    			HttpSession session = request.getSession(false);
    			if (session != null) {
    				Object mutex = WebUtils.getSessionMutex(session);
    				synchronized (mutex) {
    					return handleRequestInternal(request, response);
    				}
    			}
    		}
    
    		return handleRequestInternal(request, response);
    	}
    
    	/**
    	 * Template method. Subclasses must implement this.
    	 * The contract is the same as for {@code handleRequest}.
    	 * @see #handleRequest
    	 */
    	@Nullable
    	protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
    			throws Exception;
    
    }
    
    • 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

    4.3 JDK

    InputStream类

    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
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    其他IO、NIO类库中也有很多类似的设计。

    HttpServlet类

    这也是比较经典的应用,其定义的doPost/doGet等方法是可以让子类进行重写的
    在这里插入图片描述

    public abstract class HttpServlet extends GenericServlet
        implements java.io.Serializable
    {
        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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
            } else {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
            }
        }
    
    
        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(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
            } else {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
            }
        }
        
        protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            String method = req.getMethod();
            if (method.equals(METHOD_GET)) {
                long lastModified = getLastModified(req);
                if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
                } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                            // Round down to the nearest second for a proper compare
                            // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
                }
            } else if (method.equals(METHOD_HEAD)) {
                long lastModified = getLastModified(req);
                maybeSetLastModified(resp, lastModified);
                doHead(req, resp);
            } else if (method.equals(METHOD_POST)) {
                doPost(req, resp);
    
            } else if (method.equals(METHOD_PUT)) {
                doPut(req, resp);	
    
            } else if (method.equals(METHOD_DELETE)) {
                doDelete(req, resp);
    
            } else if (method.equals(METHOD_OPTIONS)) {
                doOptions(req,resp);
    
            } else if (method.equals(METHOD_TRACE)) {
                doTrace(req,resp);
    
            } else {
                //
                // Note that this means NO servlet supports whatever
                // method was requested, anywhere on this server.
                //
                String errMsg = lStrings.getString("http.method_not_implemented");
                Object[] errArgs = new Object[1];
                errArgs[0] = method;
                errMsg = MessageFormat.format(errMsg, errArgs);
    
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
            }
        }
        
    }
    
    • 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
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    4.4 MyBatis

    BaseExecutor是MyBatis中经典的模板方法模式应用,其主要是用来执行SQL。

    query方法可以看作是定义的主流程,doQuery方法是其留给子类实现的。

    public abstract class BaseExecutor implements Executor {
        
      // 几个do开头的方法都是留给子类实现的
      protected abstract int doUpdate(MappedStatement ms, Object parameter)
          throws SQLException;
    
      protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
          throws SQLException;
    
      protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException;
    
      protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
          throws SQLException;  
        
        
      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    
      @SuppressWarnings("unchecked")
      @Override
      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 (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          clearLocalCache();
        }
        List<E> list;
        try {
          queryStack++;
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
    }
    
    
      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
          // 具体query方式,交由子类实现
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);
        }
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
          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
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
  • 相关阅读:
    网络中冗余备份
    【JVM笔记】方法的调用—虚方法与非虚方法
    Apache Kafka与Spring整合应用详解
    MySQL复合查询
    【Python机器学习】零基础掌握SpectralCoclustering聚类
    【opencv图像处理】--3.图像运算、基本变换、仿射变换
    助企上云新举措!移动云网盘服务平台正式上线
    每日分享html之2个悬停、2个加载、1个button
    python:pyqt5案例(简易浏览器)
    【C语言学习笔记---字符串函数】
  • 原文地址:https://blog.csdn.net/CSDN_WYL2016/article/details/125840768