阅读 69

设计模式-模板模式及应用

一般生活中我们办理一件事需要一套指定的流水线。例如银行办事,需要先去营业厅取号、排队、办理业务、综合评分。这一套固有的流程,取号、排队、综合评分等都是固定的,不同的客户会办理不同的业务,只有这块是没个客户不同的。类似于这种有固定的流水线,在软件设计的过程中,也是时长被使用到的。

定义这条流水线的方法为模板方法,执行到每个环节在调取相应方法的函数。在设计模式中,此类的问题为模板方法模式,下面就通过案例来说一下模板方法的使用。

定义及结构特点

看下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 AIPJava Servlet制定的标准, 可以使用 javax.servletjavax.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的子类。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-12LITC3h-1621262835394)(evernotecid://8E645ED8-D430-4766-B866-1F39DB4714E0/appyinxiangcom/13431112/ENResource/p1467)]

这里具体的方法都由子类来实现,HttpServlet不能被实例化是个抽象类,具体的方法请求都由子类来实现,HttpServlet在这里充当模板模式的抽象类,不同子类的实现为具体的实现类,重新不同请求方法。


作者:fw19940314
链接:https://juejin.cn/post/7026601760630243336


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐