我们在编写servlet的时候,需要继承javax.servlet.http.HttpServlet然后根据需要重写父类的doGet,doPost, doPut, doDelete等等。然而doGet, doPost, doPut, doDelete这些方法是如何被调用的呢?这里Tomcat运用了模板方法模式。
父类javax.servlet.http.HttpServlet不仅实现了doGet,doPost, doPut, doDelete这些方法的默认逻辑,还实现了这些方法通用的调用规则函数voidjavax.servlet.http.HttpServlet.service(HttpServletRequestreq, HttpServletResponseresp)。这个函数如何被调用可以参考Tomcat源码分析HTTP消息处理(从connector到servlet)
protectedvoidservice(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 supportif-modified-since, no reason
// to go through further expensivelogic
doGet(req, resp);
} else {
long ifModifiedSince =req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince< (lastModified / 1000 * 1000)) {
// If the servletmod time is later, call doGet()
// Round down tothe nearest second for a proper compare
// AifModifiedSince of -1 will always be less
maybeSetLastModified(resp,lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} elseif (method.equals(METHOD_HEAD)) {
long lastModified =getLastModified(req);
maybeSetLastModified(resp,lastModified);
doHead(req, resp);
} elseif (method.equals(METHOD_POST)) {
doPost(req, resp);
} elseif (method.equals(METHOD_PUT)) {
doPut(req, resp);
} elseif (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} elseif (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} elseif (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servletsupports whatever
// method was requested, anywhere on thisserver.
//
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);
}
}
service函数就是一个模板,在模板中调用其他方法。这样子类就不需要关心各个方法的调用规则,只需重写需要的方法实现需要的逻辑即可。
HttpServlet类非常简单明了的体现了模板方法模式的思想。以下是对模板方法模式的总结,摘自模板方法模式 。
1.模板方法设计模式的意图
通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序。但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关。
模板方法模式把我们不知道具体实现的步骤封装成抽象方法,提供一个按正确顺序调用它们的具体方法(这些具体方法统称为“模板方法”),这样构成一个抽象基类。子类通过继承这个抽象基类去实现各个步骤的抽象方法,而工作流程却由父类控制。
2.模板方法模式定义及结构
模板方法模式属于行为模式的一种(GOF95)。准备一个抽象类,定义一个操作中的算法的骨架,将一些步骤声明为抽象方法迫使子类去实现。不同的子类可以以不同的方式实现这些抽象方法。
3.模板方法模式与控制反转
“不要给我们打电话,我们会给你打电话”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。模板方法模式充分的体现了“好莱坞”原则。由父类完全控制着子类的逻辑,这就是控制反转。子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。
注意:模板方法模式中,抽象类的模板方法应该声明为final的。因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。
4.模板方法模式与开闭原则
什么是“开闭原则”?
开闭原则是指一个软件实体应该对扩展开放,对修改关闭。也就是说软件实体必须是在不被修改的情况下被扩展。模板方法模式意图是由抽象父类控制顶级逻辑,并把基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象的复用,同时也遵守了开闭原则。
父类通过顶级逻辑,它通过定义并提供一个具体方法来实现,我们也称之为模板方法。通常这个模板方法才是外部对象最关心的方法。所以它必须是public的,才能被外部对象所调用。同时,因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。
5.模板方法模式与对象的封装性
面向对象的三大特性:继承,封装,多态。
对象有内部状态和外部的行为。封装是为了信息隐藏,通过封装来维护对象内部数据的完整性。使得外部对象不能够直接访问一个对象的内部状态,而必须通过恰当的方法才能访问。
在Java中,采用给对象属性和方法赋予指定的修改符(public、protected、private)来达到封装的目的,使得数据不被外部对象恶意的访问及方法不被错误调用导造成破坏对象的封装性。
注意:模板方法模式中,迫使子类实现的抽象方法应该声明为protectedabstract。
6. 模板方法和其他模式的联系
各个模式之间都有联系,模板方法也不例外,她并不是孤立存在的。模板中的那些虚方法实际上都是使用工厂方法设计模式,将父类的执行逻辑延迟到子类。有的时候模板方法里定义算法的步骤会用到策略模式,因为有的时候这个算法不止一种,比如上面的教育部规定新生报到流程这个算法,有可能教育部规定了三四种,那么我们就可以用策略模式封装这几套算法。
7.模板方法的变形
学习设计模式最忌讳的就是生搬硬套,我们上面给出的Spring的HibernateTemplate源码分析就是告诉大家设计模式一定要领会精髓,活学活用。