设计模式-模板模式及应用
一般生活中我们办理一件事需要一套指定的流水线。例如银行办事,需要先去营业厅取号、排队、办理业务、综合评分。这一套固有的流程,取号、排队、综合评分等都是固定的,不同的客户会办理不同的业务,只有这块是没个客户不同的。类似于这种有固定的流水线,在软件设计的过程中,也是时长被使用到的。
定义这条流水线的方法为模板方法,执行到每个环节在调取相应方法的函数。在设计模式中,此类的问题为模板方法模式,下面就通过案例来说一下模板方法的使用。
定义及结构特点
看下GOF《设计模式》一书中的定义:模板方法模式在一个方法中定义一个算法⻣架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
1.模板模式的结构
抽象类模板方法 定义模板执行顺序,需要执行的函数
具体类 实现模板方法中的特定方法和钩子方法
抽象类中一般有三种类型方法: 1)、普通方法,定义固有的方法 2)、抽象方法,各子类对象需要自定义实现的方法,也就是定义中提到的,推迟到子类中实现的方法。 3)、钩子方法,一般是由子类决定是否重写,来决定模板方法中是否执行某块逻辑
2.模板方法结构图:
代码案例
1.案例1
抽象类 指定模板
//抽象类 public abstract class AbstractClass { //模板方法 算法骨架 public void templateMethod() { method1(); abstractMethod(); method2(); } public void method1(){ System.out.println("模板方法1"); } public void method2(){ System.out.println("模板方法2"); } // 抽象方法 推迟到子类执行的方法 protected abstract void abstractMethod(); } 复制代码
具体实现类1
public class ConcreteClass extends AbstractClass{ @Override protected void abstractMethod() { System.out.println("具体模板方法1"); } } 复制代码
具体实现类2
public class ConcreteClass2 extends AbstractClass{ @Override protected void abstractMethod() { System.out.println("具体模板方法2"); } } 复制代码
使用方
public class Client { public static void main(String[] args) { AbstractClass abstractClass = new ConcreteClass(); abstractClass.templateMethod(); System.out.println("----------------"); AbstractClass abstractClass2 = new ConcreteClass2(); abstractClass2.templateMethod(); } } 复制代码
执行结果:
模板方法1 具体模板方法1 模板方法2 ---------------- 模板方法1 具体模板方法2 模板方法2 复制代码
执行结果中,模板方法中的普通方法method1,method2
,都是在抽象类中定义的,所有的子类对象执行模板方法时,都一样,自定义的部分在抽象方法中。这就是模板模式的基本骨架,很简单。
2.案例2
案例2,通过模拟网上购物的流程(选择商品、加入购物车、增加运费险、下单、付款),来体会一下模板模式的使用。包括钩子函数的使用。
抽象类
//抽象流程 public abstract class AbstractFlow { // 购物流程模板 public void shoppingTemplate() { choose(); addShopStore(); //钩子方法 if (hook()) { freightInsurance(); } order(); pay(); } // 商品选择 public void choose() { System.out.println("商品选选择阶段"); } // 加入购物车 public void addShopStore() { System.out.println("加入购物车"); } // 下单 public void order() { System.out.println("下单...跳转付款页面"); } // 运费险 public void freightInsurance() { System.out.println("附加运费险"); } //钩子函数 public boolean hook() { return false; } // 付款 protected abstract void pay(); } 复制代码
具体实现类1 买一双鞋子 (无需运费险)
public class ShopShoeFlow extends AbstractFlow{ @Override protected void pay() { System.out.println("购买鞋子花费:500元"); } } 复制代码
-具体实现类2 买一件T-shirt (需要运费险)
public class ShopTshirtFlow extends AbstractFlow { public boolean hook() { // 衣服的尺寸因为每个品牌都不标准,这里重写钩子函数,方便增加运费险 return true; } @Override protected void pay() { System.out.println("购买 T-shirt 花费:300元"); } } 复制代码
使用方
public class Client { public static void main(String[] args) { // 购买鞋子 AbstractFlow shopShoeFlow = new ShopShoeFlow(); shopShoeFlow.shoppingTemplate(); System.out.println("-----------"); // 购买一件T-shirt AbstractFlow shopTshirtFlow = new ShopTshirtFlow(); shopTshirtFlow.shoppingTemplate(); } } 复制代码
执行结果:
商品选选择阶段 加入购物车 下单...跳转付款页面 购买鞋子花费:500元 ----------- 商品选选择阶段 加入购物车 附加运费险 下单...跳转付款页面 购买 T-shirt 花费:300元 复制代码
案例2结构:
我们买东西其他的流程都是一样,只有在最后付款的时候,因为不同的商品最终付款的价格也是不同的。所有把付款的方法推迟到购买具体商品的子类中实现;钩子方法的使用,钩子方法是控制整个模板算法骨架中,某一部分不是必须要在整个流程中的部分。这里可以通过子类重写钩子函数的方式,来控制注册分功能是否要在整个流程中执行。
对于鞋子来说一般我们鞋码都是固定的,不同品牌之间有细微的差别,基本不会买错。但是衣服来说的话,差别就很大,不同款式(运动、休闲)不同品牌之间的尺码都不一致。买错有更大的风险,所以对买衣服增加个运费险,通过重写钩子函数来实现。
模板模式的优缺点及应用场景
优点:
它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
它在父类中提取了公共的部分代码,便于代码复用。
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点:
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度;
由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍,增加了代码的耦合性。
应用场景
整个流程有固定的环境,但是其中某部分不同
当多个子类存在公共的行为时,可以将其提取出来并集中到
需要灵活的控制整个算法固件中各部分的执行流程(钩子函数)
模板模式在源码中的使用
1.Mybatis中模板模板方法的使用
列举一例,BaseExecutor
提供了基本sql执行方法,实现了sql执行逻辑,骨架是定义好了,部分特定的实现是在子类的方法中实现的。如事务管理、缓存管理。
// 查询 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; } // 修改 @Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } clearLocalCache(); // 调用修改 return doUpdate(ms, parameter); } //...略 // 抽象方法 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; 复制代码
BaseExecutor
类实现Executor
接口中的query()、update()...
等等,都是提供一个执行流程,最终调用了本类中的抽象方法,具体的实现是放在子类中实现。
子类的实现有四个,分别不同实现了抽象类中的方法,子类的功能:
SimpleExecutor 是 Mybatis 执行 Mapper 语句时默认使用的 Executor,提供最基本的 Mapper 语句执行功能,没有过多的封装。
ReuseExecutor 提供了 Statement 重用的功能,通过 statementMap 字段缓存使用过的 Statement 对象进行重用,可以减少 SQL 预编译以及创建和销毁 Statement 对象的开销,从而提高性能。
BatchExecutor 实现了批处理多条 SQL 语句的功能,在客户端缓存多条 SQL 并在合适的时机将多条 SQL 打包发送给数据库执行,从而减少网络方面的开销,提升系统的性能。
ClosedExecutor 是个类的一个内部类
2.Servlet中模式模式的使用
Servlet AIP
是Java Servlet
制定的标准, 可以使用 javax.servlet
和 javax.servlet.http
包创建. Java Servlet
是运行在Web服务器或应用服务器上的程序,它是作为来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。 支持Http协议HttpServlet
,HttpServlet
继承于 GenericServlet
,实现了Servlet
接口,HttpServlet
提供了Http协议的请求。
//根据请求方式不同,调用不听方法 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { this.doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch (IllegalArgumentException var9) { ifModifiedSince = -1L; } if (ifModifiedSince < lastModified / 1000L * 1000L) { this.maybeSetLastModified(resp, lastModified); this.doGet(req, resp); } else { resp.setStatus(304); } } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } } // httpServlet doget 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); } } //... doPost doHead doPut ..略 复制代码
这里的模板方法就是Service()
,根据不同的请求,调用HttpServlet
中的指定方法,这里篇幅有限贴了 doGet()
方法,发现没有任何实现。会报40*
的错误,看下Httpservelet的子类。
这里具体的方法都由子类来实现,HttpServlet
不能被实例化是个抽象类,具体的方法请求都由子类来实现,HttpServlet
在这里充当模板模式的抽象类,不同子类的实现为具体的实现类,重新不同请求方法。
作者:fw19940314
链接:https://juejin.cn/post/7026601760630243336