模板方法(Template Method)
定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
适用场景:
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复。
优缺点
优点:提高复用性;提高扩展性;符合开闭原则。
缺点:类数目增加,增加了系统实现的复杂度;继承关系自身缺点,即如果父类添加新的抽象方法,所有子类都要改一遍。
应用场景
制作一节网课的步骤可以简化为4个步骤:制作PPT;录制视频;编写笔记;提供课程资料。
所有课程都需要制作PPT、录制视频,但不是每个课程都需要编写笔记,而提供的课程资料在每个课程都不尽不同(有些课程需要提供源代码,有些需要提供图片文件等)。
我们可以在抽象父类中确定整个流程的模板,并实现固定不变的步骤,而把不固定的步骤留给子类实现。除此之外,对于类似编写笔记这个不一定有的步骤,我们可以通过一个钩子方法,让子类来决定流程中其执行与否。
抽象父类,由于制作PPT、录制视频对于每节课都是必须且相同的,因此声明为final
使得子类无法对其修改,而编写笔记虽然可有可无,但是具体的操作对于所有课程也是相同的因此不需要修改,所以也声明为final
,而提供课程资料(packageCourse
方法)这一步骤则交由具体子类实现:
public abstract class ACourse {
protected final void makeCourse(){
this.makePPT();
this.makeVideo();
if(needWriteArticle()){
this.writeArticle();
}
this.packageCourse();
}
final void makePPT(){
System.out.println("制作PPT");
}
final void makeVideo(){
System.out.println("制作视频");
}
final void writeArticle(){
System.out.println("编写笔记");
}
//钩子方法
protected boolean needWriteArticle(){
return false;
}
abstract void packageCourse();
}
前端课程:
public class FECourse extends ACourse {
@Override
void packageCourse() {
System.out.println("提供课程的前端代码");
System.out.println("提供课程的图片等多媒体素材");
}
}
设计模式课程,覆盖了钩子方法,让其可以编写笔记:
public class DesignPatternCourse extends ACourse {
@Override
void packageCourse() {
System.out.println("提供课程Java源码");
}
@Override
protected boolean needWriteArticle() {
return true;
}
}
客户端类:
public class Test {
public static void main(String[] args) {
System.out.println("后端设计模式课程start---");
ACourse designPatternCourse = new DesignPatternCourse();
designPatternCourse.makeCourse();
System.out.println("后端设计模式课程end---");
System.out.println("前端设计模式课程start---");
ACourse feCourse = new FECourse();
feCourse.makeCourse();
System.out.println("前端设计模式课程end---");
}
}
JDK中的应用
我们查看java.util
下的AbstractList
抽象类:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
//...
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {
add(index++, e);
modified = true;
}
return modified;
}
//...
这里面的addAll
方法就相当于一个模板方法,它定义了这个算法的整体流程,而其具体的步骤如rangeCheckForAdd
、add
则交由子类如ArrayList
等来完成。
Servlet中的应用
Servlet是用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。
每一个Servlet都必须要实现Servlet
接口,GenericServlet
是个通用的、不特定于任何协议的Servlet,它实现了Servlet
接口,而HttpServlet
继承于GenericServlet
,实现了Servlet
接口,为Servlet
接口提供了处理HTTP协议的实现,所以我们定义的Servlet
只需要继承HttpServlet
即可。
在HttpServlet
的service
方法中,首先获得到请求的方法名,然后根据方法名调用对应的doXXX
方法,比如说请求方法为GET,那么就去调用doGet
方法;请求方法为POST,那么就去调用doPost
方法。
HttpServlet
相当于定义了一套处理HTTP请求的模板。service
方法为模板方法,定义了处理HTTP请求的基本流程,doXXX
等方法为基本步骤,根据请求方法做相应的处理,编写自定义的Servlet
时可以重写这些方法。
public abstract class HttpServlet extends GenericServlet {
//,,,
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;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
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);
}
}
//...
}
参考资料
- 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007.
- 慕课网java设计模式精讲 Debug 方式+内存分析
- 模板方法模式及典型应用