模板模式是一个提高代码复用性或者扩展的模式,本章我们将结合着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 {

}

}

那么,介绍完了模板模式的两个特点,扩展和复用,其实都是一个道理,就是在父类中定义一个算法的框架,在框架的某些执行点上,将步骤推迟到子类中去实现,这里的算法,可以定义为业务逻辑,并不特指数据结构和算法中的算法,算法框架其实就是业务框架

那么子类可以复用父类中提供的模板方法的代码

发表评论

邮箱地址不会被公开。 必填项已用*标注