设计模式-模板方法模式

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

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

相关推荐
庄小焱4 小时前
设计模式——中介者设计模式(行为型)
设计模式
庄小焱7 小时前
设计模式——备忘录设计模式(行为型)
设计模式
庄小焱7 小时前
设计模式——代理设计模式(结构型)
设计模式
哆啦A梦的口袋呀7 小时前
基于Python学习《Head First设计模式》第三章 装饰者模式
python·学习·设计模式
哆啦A梦的口袋呀7 小时前
基于Python学习《Head First设计模式》第五章 单件模式
python·学习·设计模式
季鸢8 小时前
Java设计模式之备忘录模式详解
java·设计模式·备忘录模式
摘星编程12 小时前
工厂方法模式深度解析:从原理到应用实战
java·设计模式·软件工程·工厂方法模式
何中应12 小时前
【设计模式-4.7】行为型——备忘录模式
java·设计模式·备忘录模式
suixinger_lmh1 天前
功能结构整理
unity·设计模式·c#·源代码管理
冰茶_1 天前
建造者模式:优雅构建复杂对象
设计模式·微软·c#·.netcore·建造者模式·软件开发