一、模板方法模式的核心思想
在使用 Java 的抽象类时,经常会使用到模板模式,使用很普遍。模板模式的核心:让抽象类给出程序的骨架和轮廓,在抽象类中编写主方法,并申明一些抽象方法,迫使子类实现剩余的逻辑。
如下图所示,模板类TemmplateClass 中定义了一个模板方法templateMethod(),并定义了多个操作方法让子类去实现,我们只需要调用模板方法即可实现对子类的调用。
模板类 TemplateClass.java 必须是抽象类,首先需要定义一个主方法 templateMethod(),该方法是外部调用的入口,需要申明为final的,在该方法中实现对多种模板方法的调用。同时,在该类中定义多个抽象函数,例如 operation1()、operation2()、operation3()等,这些方法强制由子类来实现。其源代码如下程序所示。
java
package behavior.templatemethod;
public abstract class TemplateClass {
public final void templateMethod() {
operation1();
operation2();
operation3();
}
abstract public void operation1();
abstract public void operation2();
abstract public void operation3();
}
模板的子类 SubClass.java 只需要继承自模板抽象类,编写它要求实现的方法即可。其源代码如下程序所示。
java
package behavior.templatemethod;
public class SubClass extends TemplateClass {
@Override
public void operation1() {
// TODO Auto-generated method stub
}
@Override
public void operation2() {
// TODO Auto-generated method stub
}
@Override
public void operation3() {
// TODO Auto-generated method stub
}
}
通过在抽象类中强制定义的 abstract函数,实现了对子类的约束,让集成它的子类按照要求实现,这就是模板方法模式的用意。
模板方法模式与策略模式相似,都是通过父类来实现一系列的具体类,但策略模式是通过接口或抽象类来为子类定义共同接口的,以可以实现对不同子类的切换:而模板方法模式则需要在抽象类中为子类定义好抽象方法,让子类来提供实现。
为了展示模板方法模式的不同,我们依然以上一节中的计算器为例,如下图所示,所有的计算类都继承自抽象的计算器类,在该抽象类中定义了各子类的模板方法。
下面来看具体的实现。
(1) 模板抽象类 AbstractCalculator.java 定义了一个模板方法 calculate(String expression, String deliString),用来执行对表达式的计算。在该方法中首先调用 split()来拆分表达式,然后调用抽象方法 calculate(int num1, int num2)来返回计算结果,该方法就是为子类定义的模板方法。其源代码如下程序所示。
java
package behavior.templatemethod;
/**
* @author Minggg
* 计算器抽象类
*/
public abstract class AbstractCalculator {
public final int calculate(String expression, String deliString) {
int[] arrayInt= split(expression, deliString);
return calculate(arrayInt[0], arrayInt[1]);
}
abstract public int calculate(int numl, int num2);
/**
* 根据计算表达式,提取待计算的数值
* @param expression 计算表达式
* @param deliString 分隔符
* @retum 待计算数值的数组
*/
public int[] split(String expression, String deliString) {
String[] array = expression.split(deliString);
int[] arrayInt = new int[2];
arrayInt[0]= Integer.parseInt(array[0]);
arrayInt[1]= Integer.parselnt(array[1]);
return arrayInt;
}
}
(2) 加法子类 Plus.java用来进行加法运算,它继承了抽象类AbstractCalculator.iava,并实现其中定义的抽象方法 calculate(intnum1,int num2),在 calculate()中计算两个数字的和。其源代码如下程序所示。
java
package behavior.templatemethod;
/**
* @author Minggg
* 加法计算类
*/
public class Plus extends AbstractCalculator {
public int calculate(int numl, int num2) {
return num1 + num2;
}
}
(3) 减法子类 Minus.java用来进行减法运算,它继承了抽象类 AbstractCalculator.java,并实现其中定义的抽象方法 calculate(int num1 ,int num2),在 calculate()中计算两个数字的差。其源代码如下程序所示。
java
package behavior.templatemethod;
/**
* @author Minggg
* 减法计算类
*/
public class Minus extends AbstractCalculator {
public int calculate(int numl, int num2) {
return num1 - num2;
}
}
(4) 乘法子类 Multiply.java用来进行乘法运算,它继承了抽象类 AbstractCalculator.java,并实现其中定义的抽象方法 calculate(int num1, int num2),在 calculate()中计算两个数字的积。其源代码如下程序所示。
java
package behavior.templatemethod;
/**
* @author Minggg
* 乘法计算类
*/
public class Multiply extends AbstractCalculator {
public int calculate(int numl, int num2) {
return num1 * num2;
}
}
(5) 除法子类 Devide.java用来进行除法运算,它继承了抽象类 AbstractCalculator.java,并实现其中定义的抽象方法 calculate(int num1,int num2),在 calculate()中计算两个数字的商。其源代码如下程序所示。
java
package behavior.templatemethod;
/**
* @author Minggg
* 除法计算类
*/
public class Devide extends AbstractCalculator {
public int calculate(int numl, int num2) {
return num1 / num2;
}
}
(6) 默认策略类 Default.java 用来进行加法、减法、乘法、除法之外的表达式运算,它继承了策略抽象类 AbstractCalculator.java,在 calculate()中直接返回0。其源代码如下程序所示。
java
package behavior.templatemethod;
/**
* @author Minggg
* 默认计算类
*/
public class Default extends AbstractCaleulator {
public int calculate(int numl, int num2) {
return 0;
}
}
使用以上模板类的方法很简单,只需要创建 AbstractCalculator 的各种子类实现,根据表达式的不同选用不同的子类,最后统一调用 AbstractCalculator的主方法 calculate(expression, deliString)取得计算结果。为了测试,我们编写一个从控制台接收用户输入表达式的程序。其源代码如下程序所示。
java
package behavior.templatemethod;
public class Test {
public static void main(String[] args) {
while (true){
// 接收表达式输入
System.out.println("准备输入:");
String expression = System.console().readLine();
// 初始化实例
ICalculator calculator;
if (expression.indexOf"+")!=-1){
calculator = new Plus();
deliString = "\\+";
} else if (expression.indexOf"-")!= -1){
calculator = new Minus();
deliString = "\\-";
} else if (expression.indexOf("*")!= -1){
calculator = new Multiply();
deliString = "\\*";
} else if (expression.indexOf"/") !=-1){
calculator = new Devide();
deliString = "\\/";
} else {
calculator = new Default();
}
// 开始运算
int value = calculator.calculate(expression);
System.out.println("="+value);
}
}
}
二、何时使用模板方法模式
所谓模板方法模式,简单说就是父类完全控制着子类的业务逻辑,而子类根据不同的业务对父类的所有抽象方法进行实现。
模板方法模式的适用场合:知道了一个算法所需的关键步骤,并确定了这些步骤的执行顺序,但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关,这时就可以使用父类控制逻辑,由子类实现算法。
模板方法模式的使用方法:定义一个abstract的Class,在这个抽象类中,至少包括一个实现模板方法的不可被子类改写的public方法(需声明为final),在这个公共方法中,实现模板方法的业务处理逻辑,同时,在这个方法中,还包括一些对本类中的抽象方法的调用:再根据业务的需要,定义各种不同的子类,并实现模板方法类的所有抽象方法。
三、Java 中的应用--HTTP 请求处理类 HttpServlet
目前在开发Java EE程序时,我们可以选用各种框架,比如Struts、Spring等,我们只需要继承它们的相关类就可以实现 HTTP的业务请求。实际上,这就是采用了模板模式,即 Struts、Spring 等定义好了程序的父类,让我们来实现对应的业务子类。这些都是由HtpServlet 这个抽象的父类来实现的,它就是采用了模板方法模式。
HtpServlet是 GenericServlet 的一个派生类,为基于 HTTP 协议的 Servlet 提供了基本的支持:HttpServlet 类包含 init()、destroy()、service()等方法。其中 init()和 destroy()方法是继承的。
1.init()方法
在 Servlet 的生命期中,仅执行一次 init()方法,它是在服务器装入 Servlet 时执行的。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问 Servlet,都不会重复执行 init()。
默认的 init()方法通常是符合要求的,但也可以用定制init()方法来覆盖它,典型的是管理服务器端资源。例如,可能编写一个定制init()只用于一次装入 GIF 图像,改进 Servlet 返回 GIF 图像和含有多个客户机请求的性能。另一个示例是初始化数据库连接。默认的init()方法设置了Servlet的初始化参数,并用它的 ServletConfg 对象参数来启动配置,因此所有覆盖init()方法的 Servlet 应调用super.init()以确保仍然执行这些任务。在调用 service()方法之前,应确保已完成了 init()方法。
2.service()方法
service()方法是 Servlet程序的入口点,当用户从浏览器调用Servlet时,Servlet将进入该方法service()包含两个参数,HttpServletRequest对象包含了客户端请求的信息,可以通过该参数取得客户端的一些信息(例如IP地址、浏览器类型等)及HTTP请求类型(例如 GET、HEAD、POST、PUI等);HttpServletResponse对象用于完成 Servlet 与客户端的交互,通过调用 HtpServletResponsegetOutputStream()客户取得向客户端输出的输出流,向客户端发送HTML页面。
service()方法是 Servlet 的核心。每当一个客户请求一个 HttpServlet 对象时,该对象的 service()方法就要被调用,而且传递给这个方法一个"请求"(ServletRequest)对象和一个"响应"(ServletResponse)对象作为参数。在 HttpServlet中已存在 service()方法。默认的服务功能是调用与HTTP 请求的方法相应的 do功能。例如,如果 HTTP请求方法为 GET,则默认情况下就调用 doGet()Servlet 应该为 Servlet 支持的 HTTP方法覆盖 do功能。因为 HtpServlet.service()方法会检查请求方法是否调用了适当的处理方法,不必要覆盖 service()方法,只需覆盖相应的 do 方法就可以了。
HTTP 的请求方式包括 GET、HEAD、POST、PUT、DELETE、OPTIONS 和 TRACE,在 HttpServlet类中分别提供了相应的服务方法。其样例如下所示:
java
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获得请求的方式
String method = req.getMethod();
if (method.equals("GET")){
doGet(req, resp);
} else if(method.equals("HEAD")) {
doHead(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
doPost(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
doPost(req, resp);
} else if (method.equals("OPTIONS")) {
doOptions(req, resp);
doPost(req, resp);
} else if (method.equals("TRACE")) {
doTrace(reg,resp);
} else {
resp.sendEror(501);
}
}
当一个客户通过 HTML 表单发出一个HTTPPOST请求时,doPost()方法被调用。与POST 请求相关的参数作为一个单独的HTTP请求从浏览器发送到服务器。当需要修改服务器端的数据时,应该使用 doPost()方法。
当一个客户通过 HTML 表单发出一个HTTP GET请求或直接请求一个URL时,doGet()方法被调用。与 GET请求相关的参数添加到 URL的后面,并与这个请求一起发送。当不会修改服务器端的数据时,应该使用 doGet()方法。
Servlet 的响应可以是下列几种类型:
- 一个输出流,浏览器根据它的内容类型(如 text/HTML)进行解释。
- 一个 HTTP 错误响应,重定向到另一个 URL、Servlet、JSP。
3.destroy()方法
destroy()方法仅执行一次,即在服务器停止且卸装Servlet时执行该方法。通常将 Servlet 作为服务器进程的一部分来关闭。默认的 destroy()方法通常是符合要求的,但也可以覆盖它,典型的是管理服务器端资源。例如,如果Servlet在运行时会累计统计数据,则可以编写一个 destroy()方法,该方法用于在未装入 Servlet时将统计数字保存在文件中。另一个示例是关闭数据库连接。
当服务器卸装Servlet时,将在所有service()方法调用完成后,或在指定的时间间隔过后调用destroy()方法。一个Servlet在运行service(方法时可能会产生其他的线程,因此请确认在调用 destroyO)方法时,这些线程已终止或完成。
4.GetServletConfig()方法
GetServletConfig()方法返回一个ServletConfig 对象,该对象用来返回初始化参数和ServletContext。SeryletContext 接口提供有关 Servlet的环境信息。
5.GetServletInfo()方法
GetServletInfo0方法是一个可选的方法,它提供有关 Servlet的信息,如作者、版本、版权。
以上的 HttpServlet 类及其子类的用法,就属于典型的模板方法模式的应用,也由此产生了众多的 Web 框架 Struts、Spring等。