概论
什么是模板模式呢?模板模式是这么定义的:定义一个操作中的算法的框架,而将这个框架中的某一些步骤延迟到子类中。使得子类可以不改变一个算法的结构。而能够重新定义该算法的某一些特定的步骤。
模板模式示例
举个例子,假如我们工作中的系统需要和许多的外部系统做交互时。由于外部系统各种各样,因此我们在发起请求时,处理方式会有所不多,有些是http请求,有些的webservice请求。或者是其他请求。然而真正在做的应该分成4个步骤。
1.参数校验。
2.封装对外的请求参数。
3.对外提交请求。
4.后置处理,例如记录操作日志。
ok,首先我们需要拥有一个抽象模板类用来定义这些步骤。
1 package com.example.pattern.template; 2 3 public abstract class AbstractProccessor { 4 5 public boolean validate(ExampleContext context) { 6 if (context == null) { 7 return false; 8 } 9 10 return true; 11 } 12 13 public abstract void prepare(ExampleContext context); 14 15 public abstract void proccess(ExampleContext context); 16 17 public abstract void after(ExampleContext context); 18 19 20 21 public void run (ExampleContext context) { 22 23 validate(context); 24 25 prepare(context); 26 27 proccess(context); 28 29 after(context); 30 31 } 32 33 }
抽象模板类AbstractProccessor 对外提供一个算法,也就是run方法。 这个算法中包含了参数校验,前置处理,提交请求,后置处理4个步骤。由于参数校验是最基本也是最公共的校验,因此在这个抽象模板中直接实现参数校验的具体方法。其他三个方法就以抽象方法的形态存在。
现在我们新增一个具体的模板类。比如我们就叫HttpProccessor。
1 package com.example.pattern.template; 2 3 public class HttpProccessor extends AbstractProccessor { 4 5 @Override 6 public void prepare(ExampleContext context) { 7 8 System.out.println("http 前置处理"); 9 10 } 11 12 @Override 13 public void proccess(ExampleContext context) { 14 15 System.out.println("http 提交请求"); 16 17 } 18 19 @Override 20 public void after(ExampleContext context) { 21 22 System.out.println("http 后置处理"); 23 24 } 25 }
我们继续新增一个模板类,命名为OtherProccessor。
1 package com.example.pattern.template; 2 3 public class OtherProccessor extends AbstractProccessor { 4 5 @Override 6 public void prepare(ExampleContext context) { 7 8 System.out.println("other 前置处理"); 9 10 } 11 12 @Override 13 public void proccess(ExampleContext context) { 14 15 System.out.println("other 提交请求"); 16 17 } 18 19 @Override 20 public void after(ExampleContext context) { 21 22 System.out.println("other 后置处理"); 23 24 } 25 }
最后我们提供一个业务场景类Client
package com.example.pattern.template; public class Client { public static void main(String[] args) { AbstractProccessor proccessor = new HttpProccessor(); proccessor.run(new ExampleContext()); } }
执行结果如下所示:
1 http 前置处理 2 http 提交请求 3 http 后置处理
模板方法的精髓在于定义一个算法,这个算法中包含一系列的步骤,这些步骤如果是公共的步骤,可以提取在抽象模板类中实现,如果是模板个性化的行为,可以延迟到子类去实现。
现在再提出一个问题。我想在有些模板中不使用后置处理,有些模板中使用后置处理,也就是要不要后置处理,交给业务场景来确定,那该怎么办呢?我们在抽象模板类中增加一个方法标记是否需要后置处理。
1 package com.example.pattern.template; 2 3 public abstract class AbstractProccessor { 4 5 public boolean validate(ExampleContext context) { 6 if (context == null) { 7 return false; 8 } 9 10 return true; 11 } 12 13 public abstract void prepare(ExampleContext context); 14 15 public abstract void proccess(ExampleContext context); 16 17 public abstract void after(ExampleContext context); 18 19 protected boolean needAfterProccessing () { 20 return true; 21 } 22 23 24 25 public void run (ExampleContext context) { 26 27 validate(context); 28 29 prepare(context); 30 31 proccess(context); 32 33 if(needAfterProccessing()) { 34 after(context); 35 } 36 } 37 38 }
needAfterProccessing方法子类可以实现重写。代码如下所示:
1 package com.example.pattern.template; 2 3 public class HttpProccessor extends AbstractProccessor { 4 5 protected boolean needAfterProccessing = false; 6 7 @Override 8 public void prepare(ExampleContext context) { 9 10 System.out.println("http 前置处理"); 11 12 } 13 14 @Override 15 public void proccess(ExampleContext context) { 16 17 System.out.println("http 提交请求"); 18 19 } 20 21 @Override 22 public void after(ExampleContext context) { 23 24 System.out.println("http 后置处理"); 25 26 } 27 28 29 @Override 30 protected boolean needAfterProccessing() { 31 return needAfterProccessing; 32 } 33 34 35 public boolean isNeedAfterProccessing() { 36 return needAfterProccessing; 37 } 38 39 public void setNeedAfterProccessing(boolean needAfterProccessing) { 40 this.needAfterProccessing = needAfterProccessing; 41 } 42 }
我们再来修改一下客户端,设置不需要进行后置处理:
1 package com.example.pattern.template; 2 3 public class Client { 4 5 public static void main(String[] args) { 6 7 AbstractProccessor proccessor = new HttpProccessor(); 8 9 ((HttpProccessor) proccessor).setNeedAfterProccessing(false); 10 11 proccessor.run(new ExampleContext()); 12 13 14 } 15 }
运行结果如下所示:
1 http 前置处理 2 http 提交请求
通过以上的方式,我们采用把相同的代码(都要进行判断是不是需要后置处理)这个判断的抽象动作封装在抽象类中,而在子类再去约束他具体的行为,这个函数就叫作钩子函数。
模板模式的优点
1.封装不变部分,扩展可变部分。
2.提取公共部分。
3.引入了钩子函数,行为由父类控制,子类实现。
模板模式的缺点
抽象类在一般情况下的作用是用来定义抽象行为,而模板模式却是将算法的处理步骤实现在抽象类中,抽象类中定义了抽象方法,交给子类去实现,而子类的实现方式会影响到父类的算法。在代码阅读上对技术有一定的要求。
模板模式的使用场景
1.多个子类有共有的方法,同时基本的处理逻辑相同。
2.重要复杂的算法,可以把算法设计成模板模式,算法中的变化的步骤定义为抽象步骤。
3.重构时,把相同或者相似的代码提取到抽象类中,并且使用钩子函数来约束父类的算法。
一般对于Java的开发者而言,对HttpServlet都很熟悉。Servlet使用来浏览器和tomcat服务器沟通的桥梁,而这个桥梁的简单封装就是HttpServlet。每一次的请求都会去调用service方法,而service方法中根据请求的参数,调用不同的方法。例如doGet或者doPost。而doGet或者doPost都是在子类中定义定位,因此这两个函数也叫作钩子函数,即子类改变父类的行为。这也是对设计模式,模板模式的最佳应用。