设计模式:模板模式,剖析模板模式在JDK、Servlet、JUnit等中 的应用

本文详细解读了模板方法模式,阐述了其如何通过定义算法骨架和延迟子类定制,实现代码复用与框架扩展。通过Servlet和JUnit例子,展示了模板模式在实际项目中的应用和优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理

模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。其定义为:模板方法,模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体架构的情况下,重新定义算法中的某些步骤。

这里的算法,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式的由来

代码实现

如下,templateMethod()函数定义为final,是为了避免子类重写它。method1()和method2()定义为abstract,是为了强迫子类去实现。不过,这些都不是必须的,在实际的项目开发中,模板模式的代码实现比较灵活,不必非得这样

public abstract class AbstractClass {
	public final void templateMethod() {
		//...
		method1();
		//...
		method2();
		//...
	}
	protected abstract void method1();
	protected abstract void method2();
}
public class ConcreteClass1 extends AbstractClass {
	@Override
	protected void method1() {
	//...
	}
	@Override
	protected void method2() {
	//...
	}
}
public class ConcreteClass2 extends AbstractClass {
	@Override
	protected void method1() {
	//...
	}
	@Override
	protected void method2() {
	//...
	}
}
AbstractClass demo = ConcreteClass1();
demo.templateMethod();

模板模式的作用

模板模式主要是用来解决复用和扩展两个问题。

复用

模板模式把一个算法中不变的流程抽象到父类的模板方法 templateMethod() 将,将可变的部分 method1()、method2() 留给子类 ContreteClass1 和 ContreteClass2 来实现。所有的子类都可以复用父类中模板方法定义的流程代码。

扩展

模板模式的第二大作用的是扩展。这里说的扩展,并不是指代码的扩展性,而是指框架的扩展性,有点类似控制反转。基于这个作用,模板模式常用在框架的开发中,让框架用户在可以不修改框架源码的情况下,定制化框架的功能。我们通过 Junit TestCase、Java Servlet 两个例子来解释一下。

Java Servlet

对于Java Web项目开发来说,常用的开发框架是SpringMVC。利用它,我们只需要关注业务代码的编写,底层的原理几乎不会涉及。但是,如果我们抛开这些高级框架来开发,就必然会用到Servlet。实际上,使用比较底层的Servlet来开发Web项目也不会难。我们只需要定义一个基础HttpServlet的类,并且重写其中的doGet()或者doPost()方法,来分别处理get和post请求。如下:

public class HelloServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
	
		this.doPost(req, resp);
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) t
	resp.getWriter().write("Hello World.");
	}
}

除此之外,我们还需要在配置文件 web.xml 中做如下配置。Tomcat、Jetty 等 Servlet 容器在启动的时候,会自动加载这个配置文件中的 URL 和 Servlet 之间的映射关系。

<servlet>
<servlet-name>HelloServlet</servlet-name>
	<servlet-class>com.xzg.cd.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>HelloServlet</servlet-name>
	<url-pattern>/hello</url-pattern>
</servlet-mapping>

当我们在浏览器中输入网址(比如,http://127.0.0.1:8080/hello )的时候,Servlet 容器会接收到相应的请求,并且根据 URL 和 Servlet 之间的映射关系,找到相应的Servlet(HelloServlet),然后执行它的 service() 方法。service() 方法定义在父类HttpServlet 中,它会调用 doGet() 或 doPost() 方法,然后输出数据(“Hello world”)到网页。

我们现在来看,HttpServlet 的 service() 函数长什么样子

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
	HttpServletRequest request;
	HttpServletResponse response;
	if (!(req instanceof HttpServletRequest &&
	res instanceof HttpServletResponse)) {
	throw new ServletException("non-HTTP request or response");
	}
	request = (HttpServletRequest) req;
	response = (HttpServletResponse) res;
	service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
	String method = req.getMethod();
	if (method.equals(METHOD_GET)) {
		long lastModified = getLastModified(req);
		if (lastModified == -1) {
			// servlet doesn't support if-modified-since, no reason
			// to go through further expensive logic
			doGet(req, resp);
		} else {
			long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
			if (ifModifiedSince < lastModified) {
				// If the servlet mod time is later, call doGet()
				// Round down to the nearest second for a proper compare
				// A ifModifiedSince of -1 will always be less
				maybeSetLastModified(resp, lastModified);
				doGet(req, resp);
			} else {
				resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
			}
		}
	} else if (method.equals(METHOD_HEAD)) {
		long lastModified = getLastModified(req);
		maybeSetLastModified(resp, lastModified);
		doHead(req, resp);
	} else if (method.equals(METHOD_POST)) {
		doPost(req, resp);
	} else if (method.equals(METHOD_PUT)) {
		doPut(req, resp);
	} else if (method.equals(METHOD_DELETE)) {
		doDelete(req, resp);
	} else if (method.equals(METHOD_OPTIONS)) {
		doOptions(req,resp);
	} else if (method.equals(METHOD_TRACE)) {
		doTrace(req,resp);
	} else {
		String errMsg = lStrings.getString("http.method_not_implemented");
		Object[] errArgs = new Object[1];
		errArgs[0] = method;
		errMsg = MessageFormat.format(errMsg, errArgs);
		resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
	}
}

从上面的代码我们可以看出,HttpServlet的service()方法就是一个模板方法,它实现了整个HTTP请求的执行流程,doGet()、doPost()是模板中可以由子类来定制的部分。实际上,这就相当于Servlet框架提供了一个扩展点(doGet()、doPost() 方法),让框架用户在不用修改Servlet框架的情况下,将业务代码通过扩展点嵌套到框架中执行。

JUnit TestCase

跟Java Servlet类似,JUnit框架也通过模板模式提供了一些功能扩展点(setUp()、tearDown() 等),让框架用户可以在这些扩展点上扩展功能。

在使用JUnit测试框架来编写单元测试的时候,我们编写的测试类都要继承框架提供的TestCase()类。在TestCase类中,runBare()函数是模板方法,它定义了执行测试用例的整体流程:先执行setUp()做些准备工作,然后执行runTest()运行真正的测试代码,最后执行tearDown()做扫尾工作。

TestCase 类的具体代码如下所示。尽管 setUp()、tearDown() 并不是抽象函数,还提供了默认的实现,不强制子类去重新实现,但这部分也是可以在子类中定制的,所以也符合模板模式的定义


public abstract class TestCase extends Assert implements Test {

public void runBare() throws Throwable {
	Throwable exception = null;
	setUp();
	try {
		runTest();
	} catch (Throwable running) {
		exception = running;
	} finally {
		try {
			tearDown();
		} catch (Throwable tearingDown) {
			if (exception == null) exception = tearingDown;
		}
	}
	if (exception != null) throw exception;
}

/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected void setUp() throws Exception {
	/**
	* Tears down the fixture, for example, close a network connection.
	* This method is called after a test is executed.
	*/
	protected void tearDown() throws Exception {
	}
}

总结

模板方法模式的一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

在模板模式经典的实现中,模板方法定义为final,可以避免被子类重写。需要子类重写的方法定义为abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。

模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过目标模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制框架的功能

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值