模板模式是一个提高代码复用性或者扩展的模式,本章我们将结合着Java Servlet,JUnit,TestCase,Java InputStream,Java AbstractList来进行讲解
模板模式,全称是模板方法设计模式,简称模板方法模式,可以说是模板方法模式在一个方法中定义一个算法骨架,并且将某些步骤推迟到子类中实现,让子类在不改变算法整体结构的情况下,重新定义算法中的步骤
这里说的算法,可以认为就是业务逻辑罢了,并不特指数据结构和算法中的算法,包含算法骨架的方法就是模板方法
对应的实现就很简单了,templateMethod()的函数定义为final,为了是避免子类重写其本身,method1和method2定义为了abstract,为了强迫子类去实现它
然后就可以根据不同的类,去扩展出不同的实现方式,当然也并不一定定义的方法就是abstract的,可以给与其一些默认实现,
public class AbstractClass {
public final void templateMethod() { //… method1(); //… method2(); //… } protected abstract void method1(); protected abstract void method2(); } public class ContreteClass1 extends AbstractClass { @Override protected void method1() { //… } @Override protected void method2() { //… } } public class ContreteClass2 extends AbstractClass { @Override protected void method1() { //… } @Override protected void method2() { //… } } AbstractClass demo = ContreteClass1(); demo.templateMethod(); |
那么就可以说一下模板模式的两大功能,复用和扩展
复用,就是将算法中不变的流程抽象到父类中,将可变的流程交给子类去实现
可以让子类在使用过程中,去父类的逻辑方法可以在执行流程中直接调用子类的实现,是一种包裹的实现方式
Java InputStream中就使用了模板模式,例如,在其中的read函数,就是读取数据的整个流程,然后暴露出了可以让子类去定制的抽象方法,这个方法被命名为了read,只不过参数和模板方法不同,在这个需要子类去实现的抽象方法中,调用了父类的read
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; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; } public abstract int read() throws IOException; } public class ByteArrayInputStream extends InputStream { //…省略其他代码… @Override public synchronized int read() { return (pos < count) ? (buf[pos++] & 0xff) : -1; } } |
其次,在Java AbstractList中,也有着对应的模板方法,例如,addAll()函数,就可以看作是模板方法,add()是子类需要重写的方法
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; } public void add(int index, E element) { throw new UnsupportedOperationException(); } |
这样,addll不需要重写,只需要调用子类新实现的add()函数即可
扩展,和上面一样,都是子类去实现某些方法,然后父类的逻辑方法可以直接调用,不实现就没法调用,但是这里的扩展可以让用户在不改变框架源码的情况下,定制化框架
在Java Servlet中,我们都会继承HttpServlet的类,并且实现其中的doGet和doPost()方法,分别处理get和post请求,那么其实在HttpServlet的类中,其接收到了一个请求的时候,会调用这个类中的service()方法,在这个service()方法中,会调用我们doGet()和doPost()方法
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException(“non-HTTP request or response”); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); } 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) { // 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); } } |
从上面可以看出,HttpServlet的service()方法是一个模板方法,实现了整个Http请求的流程,doGet和doPost是由子类定制的,在这里,就提供了一个扩展点,让框架用户在不改变Servlet的框架源码的情况下,将业务代码通过扩展点嵌入进去
同样,在测试框架,JUnit TestCase中,也通过模板模式提供了一些扩展点,setUp()函数和tearDow()函数,让其在这个扩展点上扩展功能
那么在TestCase框架的测试流程,首先必须写的runTest真正的测试代码,setUp()则可以去实现做一些准备工作,tearDown()可以做一些扫尾工作,整体调用runBare()函数
那么TestCase类的具体代码如下,整体流程中,tearDown()和setUp()是可扩展点,但是并不强制子类去实现,但是是可以在子类中定制的
public abstract class TestCase extends Assert implements Test {
public void runBare() throws Throwable { Throwable exception = null; setUp(); try { runTest(); } catch (Throwable running) { exception = running; } finally { try { tearDown(); } catch (Throwable tearingDown) { if (exception == null) exception = tearingDown; } } if (exception != null) throw exception; } /** * Sets up the fixture, for example, open a network connection. * This method is called before a test is executed. */ protected void setUp() throws Exception { } /** * Tears down the fixture, for example, close a network connection. * This method is called after a test is executed. */ protected void tearDown() throws Exception { } } |
那么,介绍完了模板模式的两个特点,扩展和复用,其实都是一个道理,就是在父类中定义一个算法的框架,在框架的某些执行点上,将步骤推迟到子类中去实现,这里的算法,可以定义为业务逻辑,并不特指数据结构和算法中的算法,算法框架其实就是业务框架
那么子类可以复用父类中提供的模板方法的代码