目录与学习目标

  1. 1:模板方式解决的问题(先了解)
  2. 2:模板模式的原理与实现
  3. 3:模板模式核心代码
  4. 4:模板模式复用例子(InputStream
  5. 5:模板模式复用例子(AbstractList
  6. 6:模板模式扩展例子(Junit
  7. 7:模板模式扩展例子(Servlet

1:模板方式解决的问题(先了解)

  1. 主要是用来解决复用和扩展两个问题
  2. 1:模板抽象类中的方法被实现类复用
  3. 2:不是指代码的扩展性,而是指框架的扩展性,有点类似我们之前讲到的控制反转
  4. 框架用户可以在不修改框架源码的情况下,定制化框架的功能

2:模板模式的原理与实现

  1. 模板方法模式在一个方法中定义一个逻辑骨架,并将某些步骤推迟到子类中实现。
  2. 模板方法模式可以让子类在不改变逻辑整体结构的情况下,重新定义逻辑中的某些步骤。

3:模板模式核心代码

  1. 1:模板方法类 (抽象类)
  2. 2ConcreteClassOne ConcreteClassTwo 继承 模板方法类(抽象类)
  3. 3:调用方 直接 调用对应子类的 模板方法
  4. 4:模板方法 里面 依次执行对应方法
  1. public abstract class AbstractClassTemplate {
  2. //使用templateMethod方法依次执行对应方法
  3. //使用final避免子类重写
  4. public final void templateMethod() {
  5. methodOne();
  6. methodTwo();
  7. }
  8. protected abstract void methodOne();
  9. protected abstract void methodTwo();
  10. }
  1. public class ConcreteClassOne extends AbstractClassTemplate {
  2. @Override
  3. protected void methodOne() {
  4. System.out.println("我是ConcreteClassOne 的 methodOne()");
  5. }
  6. @Override
  7. protected void methodTwo() {
  8. System.out.println("我是ConcreteClassOne 的 methodTwo()");
  9. }
  10. }
  1. public class ConcreteClassTwo extends AbstractClassTemplate {
  2. @Override
  3. protected void methodOne() {
  4. System.out.println("我是ConcreteClassTwo 的 methodOne()");
  5. }
  6. @Override
  7. protected void methodTwo() {
  8. System.out.println("我是ConcreteClassTwo 的 methodTwo()");
  9. }
  10. }
  1. public class TemplateDemoStart {
  2. public static void main(String[] args) {
  3. AbstractClassTemplate concreteClassOne = new ConcreteClassOne();
  4. concreteClassOne.templateMethod();
  5. AbstractClassTemplate concreteClassTwo = new ConcreteClassTwo();
  6. concreteClassTwo.templateMethod();
  7. }
  8. }

4:模板模式复用例子(InputStream)

  1. Java IO 类库中,有很多类的设计用到了模板模式,
  2. 比如 InputStreamOutputStreamReaderWriter。我们拿 InputStream 来举例说明一下。
  3. InputStream 部分相关代码贴在了下面。
  4. 在代码中,read() 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了一个可以由子类来定制的抽象方法。
  5. 不过这个方法也被命名为了 read(),只是参数跟模板方法不同。
  1. public abstract class InputStream implements Closeable {
  2. //...省略其他代码...
  3. //read 模板方法(带参数) 调用了read方法(不带参数)
  4. public int read(byte b[], int off, int len) throws IOException {
  5. if (b == null) {
  6. throw new NullPointerException();
  7. } else if (off < 0 || len < 0 || len > b.length - off) {
  8. throw new IndexOutOfBoundsException();
  9. } else if (len == 0) {
  10. return 0;
  11. }
  12. int c = read();
  13. if (c == -1) {
  14. return -1;
  15. }
  16. b[off] = (byte)c;
  17. int i = 1;
  18. try {
  19. for (; i < len ; i++) {
  20. c = read();
  21. if (c == -1) {
  22. break;
  23. }
  24. b[off + i] = (byte)c;
  25. }
  26. } catch (IOException ee) {
  27. }
  28. return i;
  29. }
  30. //read(不带参数 提供给实现类重写)
  31. public abstract int read() throws IOException;
  32. }
  1. public class ByteArrayInputStream extends InputStream {
  2. //...省略其他代码...
  3. //实现类重写了 InputStream 的 read方法
  4. @Override
  5. public synchronized int read() {
  6. return (pos < count) ? (buf[pos++] & 0xff) : -1;
  7. }
  8. }

5:模板模式复用例子(AbstractList)

  1. Java AbstractList 类中,addAll() 函数可以看作模板方法,add() 是子类需要重写的方法。
  2. 尽管没有声明为 abstract 的,但子类不重写是不能使用的。
  3. 因为该方法实现直接抛出了 UnsupportedOperationException 异常。
  1. public boolean addAll(int index, Collection<? extends E> c) {
  2. rangeCheckForAdd(index);
  3. boolean modified = false;
  4. for (E e : c) {
  5. add(index++, e);
  6. modified = true;
  7. }
  8. return modified;
  9. }
  10. public void add(int index, E element) {
  11. throw new UnsupportedOperationException();
  12. }

6:模板模式扩展例子(Junit)

  1. Junit 框架也通过模板模式提供了一些功能扩展点(setUp()、tearDown() 等),让框架用户可以在这些扩展点上扩展功能。
  2. 在使用 JUnit 测试框架来编写单元测试的时候,我们编写的测试类都要继承框架提供的 TestCase 类。
  3. TestCase 类中,runBare() 函数是模板方法:
  4. 它定义了执行测试用例的整体流程:先执行 setUp() 做些准备工作,然后执行 runTest() 运行真正的测试代码,最后执行 tearDown() 做扫尾工作。
  5. 尽管 setUp()、tearDown() 并不是抽象函数,还提供了默认的实现,不强制子类去重新实现,但这部分也是可以在子类中定制的,所以也符合模板模式的定义。
  1. public abstract class TestCase extends Assert implements Test {
  2. public void runBare() throws Throwable {
  3. Throwable exception = null;
  4. setUp();
  5. try {
  6. runTest();
  7. } catch (Throwable running) {
  8. exception = running;
  9. } finally {
  10. try {
  11. tearDown();
  12. } catch (Throwable tearingDown) {
  13. if (exception == null) exception = tearingDown;
  14. }
  15. }
  16. if (exception != null) throw exception;
  17. }
  18. /**
  19. * Sets up the fixture, for example, open a network connection.
  20. * This method is called before a test is executed.
  21. */
  22. protected void setUp() throws Exception {
  23. }
  24. /**
  25. * Tears down the fixture, for example, close a network connection.
  26. * This method is called after a test is executed.
  27. */
  28. protected void tearDown() throws Exception {
  29. }
  30. }

7:模板模式扩展例子(Servlet)

  1. 对于 Java Web 项目开发来说,常用的开发框架是 SpringMVC
  2. 我们只需要关注业务代码的编写,底层的原理几乎不会涉及。但是,如果我们抛开这些高级框架来开发 Web 项目,必然会用到 Servlet
  3. 使用比较底层的 Servlet 来开发 Web 项目也不难。我们只需要定义一个继承 HttpServlet 的类,
  4. 并且重写其中的 doGet() doPost() 方法,来分别处理 get post 请求。
  1. public class HelloServlet extends HttpServlet {
  2. @Override
  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  4. this.doPost(req, resp);
  5. }
  6. @Override
  7. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  8. resp.getWriter().write("Hello World.");
  9. }
  10. }
  1. 同时需要在配置文件 web.xml 中做如下配置。
  2. TomcatJetty Servlet 容器在启动的时候,会自动加载这个配置文件中的 URL Servlet 之间的映射关系。
  3. 当我们在浏览器中输入网址(比如,http://127.0.0.1:8080/hello )的时候,
  4. Servlet 容器会接收到相应的请求,并且根据 URL Servlet 之间的映射关系,找到相应的 ServletHelloServlet),然后执行它的 service() 方法。
  5. service() 方法定义在父类 HttpServlet 中,它会调用 doGet() doPost() 方法,然后输出数据(“Hello world”)到网页。
  1. <servlet>
  2. <servlet-name>HelloServlet</servlet-name>
  3. <servlet-class>com.HelloServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>HelloServlet</servlet-name>
  7. <url-pattern>/hello</url-pattern>
  8. </servlet-mapping>
  1. HttpServlet service() 方法就是一个模板方法,它实现了整个 HTTP 请求的执行流程,
  2. doGet()、doPost() 是模板中可以由子类来定制的部分。
  3. 实际上,这就相当于 Servlet 框架提供了一个扩展点(doGet()、doPost() 方法),
  4. 让框架用户在不用修改 Servlet 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。
  1. public void service(ServletRequest req, ServletResponse res)
  2. throws ServletException, IOException
  3. {
  4. HttpServletRequest request;
  5. HttpServletResponse response;
  6. if (!(req instanceof HttpServletRequest &&
  7. res instanceof HttpServletResponse)) {
  8. throw new ServletException("non-HTTP request or response");
  9. }
  10. request = (HttpServletRequest) req;
  11. response = (HttpServletResponse) res;
  12. service(request, response);
  13. }
  14. protected void service(HttpServletRequest req, HttpServletResponse resp)
  15. throws ServletException, IOException
  16. {
  17. String method = req.getMethod();
  18. if (method.equals(METHOD_GET)) {
  19. long lastModified = getLastModified(req);
  20. if (lastModified == -1) {
  21. // servlet doesn't support if-modified-since, no reason
  22. // to go through further expensive logic
  23. doGet(req, resp);
  24. } else {
  25. long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
  26. if (ifModifiedSince < lastModified) {
  27. // If the servlet mod time is later, call doGet()
  28. // Round down to the nearest second for a proper compare
  29. // A ifModifiedSince of -1 will always be less
  30. maybeSetLastModified(resp, lastModified);
  31. doGet(req, resp);
  32. } else {
  33. resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  34. }
  35. }
  36. } else if (method.equals(METHOD_HEAD)) {
  37. long lastModified = getLastModified(req);
  38. maybeSetLastModified(resp, lastModified);
  39. doHead(req, resp);
  40. } else if (method.equals(METHOD_POST)) {
  41. doPost(req, resp);
  42. } else if (method.equals(METHOD_PUT)) {
  43. doPut(req, resp);
  44. } else if (method.equals(METHOD_DELETE)) {
  45. doDelete(req, resp);
  46. } else if (method.equals(METHOD_OPTIONS)) {
  47. doOptions(req,resp);
  48. } else if (method.equals(METHOD_TRACE)) {
  49. doTrace(req,resp);
  50. } else {
  51. String errMsg = lStrings.getString("http.method_not_implemented");
  52. Object[] errArgs = new Object[1];
  53. errArgs[0] = method;
  54. errMsg = MessageFormat.format(errMsg, errArgs);
  55. resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
  56. }
  57. }

项目连接

  1. 请配合项目代码食用效果更佳:
  2. 项目地址:
  3. https://github.com/hesuijin/hesujin-design-pattern
  4. Git下载地址:
  5. https://github.com.cnpmjs.org/hesuijin/hesujin-design-pattern.git
  6. demo-study模块 behavior_design_pattern Template