1.引子
上一篇我们分享了行为型设计模式中的观察者模式,这一篇我们接着来聊一聊模板设计模式,模板设计模式,全名叫做模板方法设计模式。
模板设计模式,是一个在实际应用中应用得非常广泛的设计模式。它的字面语义可以这么去理解,利用面向对象编程的继承特性,在父类中定义算法的整体骨架(即模板方法),将可变部分延迟到子类中去实现(即扩展点) 。
因此,通过模板设计模式,我们可以获得的直观收益有:复用性、及扩展性。
在今天的文章中,我将通过实际案例给你演示模板设计模式的复用性以及扩展性。并且我们说模板设计模式应用非常广泛,非常适合应用于封装框架层面,我还将带着你一起看一看jdk的相关源码,看一看jdk类库中模板设计模式的一些应用案例。比如说抽象类机制、IO流、Servlet等。
这些都是你所熟悉的对吧,那就让我们开始吧。
2.案例
2.1.模板模式样例代码
关于模板设计模式,你需要关注三个点:
- 利用继承关系
- 在父类中,定义算法骨架,即模板方法
- 在子类中,实现可变部分,即实现扩展点
csharp
// 父类
public abstract class Parent{
//定义算法骨架(模板方法)
public void skeletonMethod(){
......
// 扩展点1
extMethod1();
// 扩展点2
extMethod2();
......
}
// 抽象方法扩展点1,延迟到子类实现
public abstract void extMethod1();
// 抽象方法扩展点2,延迟到子类实现
public abstract void extMethod2();
}
// 子类
public class Sub extends Parent{
// 重写扩展点1
@Overried
public void extMethod1(){
// 子类实现扩展点1,具体业务逻辑
}
// 重写扩展点2
@Overried
public void extMethod2(){
// 子类实现扩展点2,具体业务逻辑
}
}
// 应用代码
public class Test{
public static void main(String[] args){
Parent obj = new Sub();
obj.skeletonMethod();
}
}
模板方法设计模式的代码实现非常简单,相信你一看就能明白。我们在父类Parent中定义了算法骨架方法skeletonMethod(),将扩展点方法extMethod1()、extMethod2()延迟到了子类Sub中去实现。
简单的代码实现,完全体现了模板设计模式的精髓:复用性,及扩展性
- 利用继承关系,收益代码的复用性,所有子类复用skeletonMethod方法
- 将可变部分,延迟到子类实现,收益代码的扩展性,扩展点extMethod1、extMethod2方法
2.2.IO类库中模板模式的应用
关于jdk的IO类库,你不应该感到陌生,在大多数应用中几乎都会涉及到。从字节流InputStream、OutputStream,到字符流Reader、Writer。并且在前面结构型设计模式中,我们从装饰器设计模式的角度分享过,今天我们从模板设计模式的角度,再来看一看IO类库中,是如何使用模板设计模式的。
我将抽取InputStream中的read方法,带着你一起来看一看。我在代码中配备了相关的注释,方便你理解
java
// 抽象父类InputStream
public abstract class InputStream implements Closeable {
......省略其它代码
// 算法骨架,模板方法
public int read(byte b[], int off, int len) throws IOException {
// 参数合法性检测
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
// 【重点关注】调用扩展点 read方法
int c = read();
// 读取不到数据,返回 -1
if (c == -1) {
return -1;
}
// 将读取到的字节数据,存入b字节数组中
b[off] = (byte)c;
int i = 1;
try {
// 循环读取数据
for (; i < len ; i++) {
// 【重点关注】,调用扩展点 read方法
c = read();
// 数据读取完毕,退出循环
if (c == -1) {
break;
}
// 将读取到的字节数据,存入b字节数组中
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
// 返回读取到的字节数量
return i;
}
// 扩展点,延迟到子类实现
/*
*<p> A subclass must provide an implementation of this method.
*翻译官方英文注释:子类必须提供read()方法的实现
*/
public abstract int read() throws IOException;
......省略其它代码
}
2.3.Web Servlet中模板模式的应用
从许多java开发朋友来说,我们大多数都是java web开发,那么对于Servlet我们都会非常熟悉。不管是早些时候直接写过Servlet、jsp的朋友,还是目前基于框架开发的朋友(struts、springmvc、springboot),我们对Servlet都一定是在人群中多看过几眼的对吧
那么你还记得,基于Servlet开发,有哪些关键步骤吗?
- 编写一个业务Servlet,继承自HttpServlet
- 在业务Servlet中,重写doGet,doPost方法
- 在web.xml文件中,配置servlet mapping映射
你看就是这么简单,当然今天我们基于框架开发,这些事情都不需要做了,由框架代劳。但是我还是想建议你关注一下框架的底层实现,万变不离其宗,懂了底层才算是打通了任督二脉,也才算得上是一个合格的技术开发人员。比如如果你们的项目使用了springmvc,你可以去看看DispatcherServlet的源代码,并且关注一下原生Servlet的体系结构,我截一个Servlet的类结构图给你作为参考
接下来,我们一起来看一下Servlet中模板模式的应用,代码比较简单,比较好理解,我在关键点配备了注释
kotlin
// Servlet接口
public interface Servlet {
// service方法
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
// 抽象类
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
// service方法未实现,标记为抽象方法
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
// 提供给我们继承的父类HttpServlet
public abstract class HttpServlet extends GenericServlet {
...省略其它代码
// 实现了service方法,该方法即模板模式中的算法骨架
// 根据http请求方法中的method属性,调用扩展点方法:
// doGet/doHead/doPost/doPut/doDelete/doOptions/doTrace
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
...省略其它代码
}