设计模式之道模板方法模式

2023年 9月 25日 83.5k 0

模板方法模式

简介

模板方法模式是一种行为型设计模式,该设计模式的核心在于通过抽象出一套相对标准的处理步骤,并可灵活的将任意步骤交给子类去进行扩展,使得可以在不改变整体业务处理流程的前提下,通过定义不同的子类实现即可完成业务处理的扩展。

我们可以举个简单的例子,比如对于下面定义的method方法中调用的a、b、c三个子方法,可以通过不同的子类实现来完成不同业务逻辑的处理。

public abstract class Temp {

    public final void method() {
        a();
        b();
        c();
    }

    protected abstract void c();

    protected abstract void b();

    protected abstract void a();

}

还可以这样定义,此时相当于b方法在父类中有一套默认的处理,子类可以根据需要选择重写或者不重写。

public abstract class Temp {

    public final void method() {
        a();
        b();
        c();
    }

    protected abstract void c();

    protected void b() {
        // 默认处理逻辑。。。
    }

    protected abstract void a();

}

当然,还可以将b方法声明为private或者加上final关键字从而禁止子类重写,此时b方法的逻辑就完全由父类统一管理。

public abstract class Temp {

    public final void method() {
        a();
        b();
        c();
    }

    protected abstract void c();

    private void b() {
        // 固定处理逻辑。。。
    }

    protected abstract void a();

}

作用

模板方法模式主要有两大作用:复用和扩展。

复用:复用指的是像method这样的方法,所有子类都可以拿来使用,复用该方法中定义的这套处理逻辑。

扩展:扩展的能力就更加强大了,狭义上可以针对代码进行扩展,子类可以独立增加功能逻辑,而不影响其他的子类,符合开闭原则,广义上可以针对整个框架进行扩展,比如像下面这段代码逻辑:

public class Temp {

    public final void method() {
        a();
        b();
        c();
        d();
    }

    protected void c() {
        // 默认处理逻辑。。。
    };

    private void b() {
        // 固定处理逻辑。。。
    }

    protected void a() {
         // 默认处理逻辑。。。
    }
    
    protected void d() {
        // 强制子类必须重写
        throw new UnsupportedOperationException();
    }

}

框架默认可以直接使用,但同时也预留了acd三个方法的扩展能力,且d方法还通过抛出异常的方式,强制要求子类必须重写,所以现在完全可以通过方法重写的方式实现框架的功能扩展。
这种框架扩展的方式的典型案例就是Servlet中定义的service方法,该方法分别预留了doGetdoPost等扩展方法。

模板方法模式的缺点

从另一个角度来说,设计模式本身实际上并不存在什么缺点,真正导致出现这些问题的原因还是使用设计模式的方式,尤其是新手在刚了解到设计模式的时候,往往会试图到处找场景去套用各种设计模式,甚至一个方法能用上好几种,这就是典型的手里拿个锤子,看什么都是钉子。所以,如果按照这样的使用方式,通常就会导致子类或者实现类非常多,但逻辑却很少,或相似;方法为了兼容各种场景而过于抽象,导致代码复杂度增加,可阅读性也变差。

针对模板方式模式来说,因为通常情况下是通过继承机制来实现业务流程的不变部分和可变部分的分离,因此,如果可变部分的业务逻辑并不复杂,或者不变部分和可变部分的关系不清晰时,就不适合用模板方法模式了。

模板方法模式的应用场景

业务的整体处理流程是固定的,但其中的个别部分是易变的,或者可扩展的,此时就可以使用模板方法模式,下面我们分别举一些常见的业务场景和开源框架的应用来说明。

业务场景

订单结算场景

订单结算在电商平台是非常常见的功能,整个结算过程一定会包含:订单生成、库存校验、费用计算、结果通知,但比如其中费用计算则可能在优惠券、折扣、运费等地方又有所不同,因此可以将整个结算过程抽象为一个模板类,具体的结算类只需要继承该模板类,并实现具体的计算规则即可。

任务活动场景

常见的任务活动,主要包含三步骤:任务事件接收、任务规则匹配、任务奖励触发,而往往事件接收和奖励触发都是比较统一的,规则匹配则跟具体的任务相关,所以可以用模板方法模式来实现。

开源框架中的应用

Spring MVC

handleRequestInternal由子类实现

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;

}

MyBatis

BaseExecutorMyBatis中经典的模板方法模式应用,其主要是用来执行SQLquery方法是模板方法的主流程,doQuery方法是其留给子类实现的。

image.png

public abstract class BaseExecutor implements Executor {
    
  // 几个do开头的方法都是留给子类实现的
  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  protected abstract List doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract  Cursor doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;  
    
    
  @Override
  public  List 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  List 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 list;
    try {
      queryStack++;
      list = resultHandler == null ? (List) 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  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List 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;
  }

JDK AbstractCollection抽象类

AbstractCollection中实现了Set接口中定义的addAll方法,该方法又是基于add方法来实现的,具体代码如下所示:

public boolean addAll(Collection c) {
        return s.containsAll(c);
    }

    @Override
    public boolean addAll(Collection c) {
        return s.retainAll(c);
    }

    @Override
    public boolean removeAll(Collection c) {
        return s.removeAll(c);
    }

    @Override
    public void clear() {
        s.clear();
    }
}
class CountSet extends ForwardingSet {

private int addCount = 0;

public CountSet(Set s) {
super(s);
}

@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}

@Override
public boolean addAll(Collection

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论