定义
定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板方式使得子类在不改变算法结构的情况下,重新定义算法的某些步骤。是基于继承的代码复用的基本技术。
简而言之,完成一件事情,有固定的数个步骤,但是会有步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。固定不变的步骤可以抽取到父类中,需要变化的步骤定义为抽象方法由子类实现。
类型
行为型
适用场景
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
- 各子类中公共的行为被提取出来并集中到一个公共父类,从而避免代码重复
优点
- 提高复用性(固定不变的方法定义在父类中,由一个固定的顺序调用方法)
- 提高可扩展性(子类根据自己的要求去实现固定顺序中的抽象方法)
- 符合开闭原则
缺点
- 类数目增加
- 增加了系统实现的复杂度
- 继承的缺点,如果父类添加了的新的抽象方法,所有子类都要修改
UML类图
抽象(Game)角色的作用:
- 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
- 定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体(Cricket)角色的作用:
- 实现父类所定义的一个或多个抽象方法。
- 每一个具体模板角色可以给出抽象方法的不同实现,从而使得顶级逻辑的实现各不相同。
代码Demo
业务场景:以制作一门课程为例,需要先有ppt,再录制视频,然后打包素材,最后写手记;ppt,video,手记都是必须的,但是素材就不一样了,不同课程的素材组成就不同。
UML类图
- 抽象类AbstractCourse
public abstract class AbstractCourse {
public final void makeCourse() {
makePPT();
makeVideo();
if (havaNotes()){
makeNotes();
}
doMaterial();
}
public final void makePPT(){
System.out.println("制作ppt");
}
public final void makeVideo(){
System.out.println("录制视频");
}
public boolean havaNotes(){
return true;
}
public final void makeNotes(){
System.out.println("写手记");
}
public abstract void doMaterial();
}
makePPT,makeVideo和makeNotes都是模板中固定不变的方法,所以需要加上final以防止子类重写,这里haveNotes是对"钩子"的使用,可以模板的扩展性,makeCourse方法加上final可以有效防止重写所带来步骤顺序的改变。
- 具体类ConcreteCourse1
public class ConcreteCourse1 extends AbstractCourse {
@Override
public void doMaterial() {
System.out.println("图片,视频,PPT,手记");
}
}
- 具体类ConcreteCourse2
public class ConcreteCourse extends AbstractCourse {
@Override
public void doMaterial() {
System.out.println("视频,PPT");
}
@Override
public boolean havaNotes() {
return false;
}
}
源码中的模板方式
HttpService类提供了一个service()方法,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。这些do方法需要由HttpServlet的具体子类提供,因此这是典型的模板方法模式。下面是service()方法的源代码:
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 / 1000 * 1000)) {
// 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 {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
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);
}
}