(一)模板方法概述
模板方法Gof的定义是:在一个方法里定义算法的骨架,将一些步骤延迟到其子类。如下图:
AbstractClass主要是定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤;同时将公用的代码移植到TemplateMethod中,实现的代码的公用。
我们来看一下模板方法的代码:
<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">/// <summary>
/// 抽象类
/// </summary>
public abstract class AbstractClass
{
// 一些抽象行为,放到子类去实现
public abstract void PrimitiveOperation1();
public abstract void PrimitiveOperation2();
/// <summary>
/// 模板方法,给出了逻辑的骨架,而逻辑的组成是一些相应的抽象操作,它们推迟到子类去实现。
/// </summary>
public void TemplateMethod()
{
PrimitiveOperation1();
PrimitiveOperation2();
Console.WriteLine("Done the method.");
}
}
/// <summary>
/// 具体类,实现了抽象类中的特定步骤
/// </summary>
public class ConcreteClassA : AbstractClass
{
/// <summary>
/// 与ConcreteClassB中的实现逻辑不同
/// </summary>
public override void PrimitiveOperation1()
{
Console.WriteLine("Implement operation 1 in Concreate class A.");
}
/// <summary>
/// 与ConcreteClassB中的实现逻辑不同
/// </summary>
public override void PrimitiveOperation2()
{
Console.WriteLine("Implement operation 2 in Concreate class A.");
}
}
/// <summary>
/// 具体类,实现了抽象类中的特定步骤
/// </summary>
public class ConcreteClassB : AbstractClass
{
/// <summary>
/// 与ConcreteClassA中的实现逻辑不同
/// </summary>
public override void PrimitiveOperation1()
{
Console.WriteLine("Implement operation 1 in Concreate class B.");
}
/// <summary>
/// 与ConcreteClassA中的实现逻辑不同
/// </summary>
public override void PrimitiveOperation2()
{
Console.WriteLine("Implement operation 2 in Concreate class B.");
}
}
客户端的代码:
class Program
{
static void Main(string[] args)
{
// 声明抽象类
AbstractClass c;
// 用ConcreteClassA实例化c
c = new ConcreteClassA();
c.TemplateMethod();
// 用ConcreteClassB实例化c
c = new ConcreteClassB();
c.TemplateMethod();
Console.Read();
}
}
</span></span></span>
结果显示:
(二)模板方法的实际应用
我们来看一个模板方法的应用
假设教育部规定了高校新生报到的流程(这好比写好了算法的骨架),流程为凭录取通知书到教务处报到->缴费->本院系报到(获取自己的专业班级信息)->教材科领取教材。
public class 高校
{
protected abstract void 教务处报到();
protected abstract void 缴费();
protected abstract 专业等信息 本院系报到();
protected abstract 教材 教材科发教材();
//这就是一个模板方法,定义了算法的骨架
public final void 报到()
{
教务处报到();
缴费();
本院系报到();
教材科发教材();
}
}
public class 高校
{
protected abstract void 教务处报到();
protected abstract void 缴费();
protected abstract 专业等信息 本院系报到();
protected abstract 教材 教材科发教材();
//这就是一个模板方法,定义了算法的骨架
public final void 报到()
{
教务处报到();
缴费();
本院系报到();
教材科发教材();
}
}
但是各个具体的院校因为自己的情况对这个步骤作进一步补充:清华大学(该校信息技术及其发达,处处透露出信息化气息)。清华大学的学生领取的通知书是一张磁卡,学生拿着磁卡到教务处报到只需要去刷卡即可,该卡还具有银行支付功能,学生只需要向该卡转入学费就可以完成缴费,但是必须是先到教务处报到后该卡才生效。学生缴费以后由电子系统自动到学生院系报到并通过手机短信方式将学生专业班级信息发送给学生,完成后由学校的物流配送系统将学生教材送到学生寝室。
<span style="font-family:SimSun;font-size:18px;">public class 清华大学 extends 高校
{
protected void 教务处报到()
{
//刷卡
}
protected void 缴费()
{
//银行卡转账
}
protected 专业等信息 本院系报到()
{
//系统自动将学生信息转入到院系信息系统
//系统将学生专业班级信息发送到学生手机
return 手机短信方式的专业等信息
}
protected 教材 教材科发教材()
{
//学校物流配送系统送教材
return 教材
}
}
</span>
北京大学(该校认为作为有深厚文化底蕴的高校,应该注重人文气息,不能用冷冰冰的电子设备代替人工,所以该校报到过程全部人工化)该校通知书为普通纸片,到教务处报到需要学生签字处长签字,然后到财务处缴纳学费,然后到院系见书记一面,给了你一张有专业班级等信息的卡片,最后拿个袋子到教材科装教材回寝室了。
<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;">public class 北京大学 extends 高校
{
protected void 教务处报到()
{
//学生签字
//教务处处长签字
//报到完毕
}
protected void 缴费()
{
//到财务处缴纳现金
}
protected 专业等信息 到本院系报到()
{
//见到院书记
return 纸片方式的专业等信息
}
protected 教材 教材科发教材()
{
//自备袋子到教材科
return 教材
}
}
</span></span>
以上是两个高校针对教育部颁发的新生报到算法作出自己具体的实现。那么全国有这么多高校又有多少种实现呢?让教育部分别给每所高校规定新生报到算法,这样是不现实的,如果教育部给所有高校规定同一个样子的报到流程就失去了灵活性,毕竟要考虑实际嘛。
通过上面的描述是不是觉得模板方法好像是:上有政策,下有对策?对,模板方法就是上面的这个政策,各个下级有自己对政策中具体步骤的实现。模板方法就像我们的宪法一样,对其他所有法律指定了一些大的框架,要制订新的法律必须依照这个框架。
(三)Servlet中模板方法的应用:
HttpServlet继承了GenericServlet类(Servlet的祖先类)HttpServlet主要方法:
1.service方法,当请求一个Servlet首先到达该方法,该方法再分发到相应的处理方法;
2.请求处理方法:(分别对应http协议的7种请求)
a.doGet—————————————————响应Get请求
b.dopost————————————————响应post请求
c.doPut—————————————————用户http1.1协议
d.doDelete———————————————用户http1.1协议
e.doHead————————————————仅响应Get请求头部
f.doOptions——————————————用户http1.1协议
g.doTrace————————————————用户http1.1协议
在java-Servlet中经常使用doget和dopost方法,以这两个方法为例讲述一下模板方法的应用和不同。
下图就是模板方法再Servlet中的应用:
当客户端请求一个servlet的时候首先被调用的是service方法。Service方法就是定义骨架的方法,在service方法中会根据HTTP请求的类型(GET,POST还是其他)来调用具体的doGet和doPost等方法;即实际的处理委派给了doGet和doPost等方法,这些子类的方法来最终处理浏览器的请求。具体可看一下HttpServlet源代码。
当客户端要提交相关数据的时候,servlet会接收相关数据,会拿到客户端的所有信息,这些都是通过HTTP协议传过来的。传过来之后,TomCate会new一个HTTPServletRequest来解析HTTP协议,还会创建一个HttpServletResponse对象(响应对象,可以向浏览器写入数据),然后就会调用真正的Servlet,而这个Servlet只new一次,调用相关的方法,获得页面的数据,再调用业务逻辑。
(四)servlet并不是严格的按照模板方法来设计的
我们常说具体问题具体分析,在这里也是。servlet讲模板方法的精髓应用到了,但是却没有完全按照模板方法设计。
1 子类可以重写service()方法
模板方法模式是通过把不变的行为搬移到超类,去除了子类中的重复代码。所以超类中的模板方法一般情况下是不会被重写的。模板方法允许子类重新定义算法的某些 步骤,而不改变算法的结构。但是在servlet中的service方法中是可以被子类重写的。当子类重写了service方法之后就破坏掉了原本定义好的模板方法。
2 doGet和doPost方法有默认的实现
在HttpServlet类中对于这两个被子类具体实现的方式是有实现的。下面是HttpServlet对于这两个实现的代码:
<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;">protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
</span></span>
这些说明在使用设计模式的时候并不是生搬硬套,而是根据具体的情况灵活变动。但是其本质是不变的。