设计模式-模板方法模式

一、模板方法模式的核心思想

在使用 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等。

相关推荐
诸葛悠闲1 小时前
设计模式——桥接模式
设计模式·桥接模式
捕鲸叉6 小时前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
小小小妮子~6 小时前
框架专题:设计模式
设计模式·框架
先睡6 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
Damon_X14 小时前
桥接模式(Bridge Pattern)
设计模式·桥接模式
越甲八千19 小时前
重温设计模式--享元模式
设计模式·享元模式
码农爱java20 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式
越甲八千20 小时前
重温设计模式--中介者模式
windows·设计模式·中介者模式
犬余21 小时前
设计模式之桥接模式:抽象与实现之间的分离艺术
笔记·学习·设计模式·桥接模式
Theodore_10221 天前
1 软件工程——概述
java·开发语言·算法·设计模式·java-ee·软件工程·个人开发