The Beauty of Design Patterns 58-Template Mode (Part 1): Analysis of the Application of Template Mode in JDK, Servlet, JUnit, etc.

58 | Template mode (Part 1): Analysis of the application of template mode in JDK, Servlet, JUnit, etc.

In the last two classes, we learned the first behavioral design pattern, the observer pattern. For different application scenarios, we explained different implementation methods, including synchronous blocking, asynchronous non-blocking implementations, and intra-process and inter-process implementations. In addition, I also took you to implement a simple EventBus framework.

Today, we will learn another behavioral design pattern, the template pattern. We have repeatedly emphasized that the principles and implementation of most design patterns are very simple. The difficulty is to grasp the application scenarios and figure out what problems can be solved. The template pattern is no exception. The template mode is mainly used to solve two problems of reuse and extension. Today we will combine four examples of Java Servlet, JUnit TestCase, Java InputStream, and Java AbstractList to explain these two functions in detail.

Without further ado, let's officially start today's study!

Principle and Implementation of Template Mode

Template mode, the full name is template method design mode, English is Template Method Design Pattern. In the book "Design Patterns" by GoF, it is defined as follows:

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

Translated into Chinese is: the template method pattern defines an algorithm skeleton in a method, and defers certain steps to subclasses. The template method pattern allows subclasses to redefine certain steps in an algorithm without changing the overall structure of the algorithm.

The "algorithm" here can be understood as "business logic" in a broad sense, and does not specifically refer to the "algorithm" in data structures and algorithms. The algorithm skeleton here is the "template", and the method containing the algorithm skeleton is the "template method", which is also the origin of the name of the template method pattern.

The principle is very simple, and the code implementation is even simpler. I wrote a sample code, as shown below. The templateMethod() function is defined as final to prevent subclasses from overriding it. method1() and method2() are defined as abstract to force subclasses to implement. However, these are not necessary. In the actual project development, the code implementation of the template mode is more flexible. When we talk about the application scenarios later, we will have a specific reflection.

public abstract class AbstractClass {
  public final void templateMethod() {
    //...
    method1();
    //...
    method2();
    //...
  }

  protected abstract void method1();
  protected abstract void method2();
}

public class ConcreteClass1 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }

  @Override
  protected void method2() {
    //...
  }
}

public class ConcreteClass2 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }

  @Override
  protected void method2() {
    //...
  }
}

AbstractClass demo = ConcreteClass1();
demo.templateMethod();

Template mode role one: reuse

At the beginning of the article, we mentioned that the template mode has two major functions: reuse and extension. Let's first look at its first function: reuse.

The template mode abstracts the invariable process of an algorithm into the template method templateMethod() of the parent class, and leaves the variable parts method1() and method2() to the subclasses ContreteClass1 and ContreteClass2 to implement. All subclasses can reuse the process code defined by the template method in the parent class. Let's understand it more intuitively through two small examples.

1.Java InputStream

In the Java IO class library, many classes are designed using the template pattern, such as InputStream, OutputStream, Reader, and Writer. Let's take InputStream as an example.

I posted the relevant code of the InputStream part below. In the code, the read() function is a template method that defines the entire process of reading data and exposes an abstract method that can be customized by subclasses. However, this method is also named read(), but the parameters are different from the template method.

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;
  }
}

2.Java AbstractList

In the Java AbstractList class, the addAll() function can be regarded as a template method. add() is a method that subclasses need to rewrite. Although it is not declared as abstract, the function implementation directly throws an UnsupportedOperationException. The premise is that it cannot be used if the subclass does not override it.

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();
}

Template mode role two: extension

The second major role of the template mode is to expand. The extension mentioned here does not refer to the extensibility of the code, but the extensibility of the framework, which is a bit similar to the inversion of control we mentioned before. You can combine it with Section . Based on this role, the template mode is often used in the development of the framework, allowing framework users to customize the functions of the framework without modifying the source code of the framework. Let's explain it through two examples of Junit TestCase and Java Servlet.

1.Java Servlet

For Java Web project development, the commonly used development framework is SpringMVC. Using it, we only need to focus on the writing of business code, and the underlying principles are hardly involved. However, if we put aside these advanced frameworks to develop Web projects, Servlets will inevitably be used. In fact, it is not difficult to develop Web projects using the lower-level Servlet. We only need to define a class that inherits HttpServlet, and rewrite the doGet() or doPost() method in it to handle get and post requests respectively. A specific code example is as follows:

public class HelloServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello World.");
  }
}

In addition, we also need to do the following configuration in the configuration file web.xml. Servlet containers such as Tomcat and Jetty will automatically load the mapping relationship between URL and Servlet in this configuration file when they start.

<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.xzg.cd.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

When we enter the URL in the browser (for example, http://127.0.0.1:8080/hello ), the Servlet container will receive the corresponding request, and find the corresponding Servlet according to the mapping relationship between the URL and the Servlet (HelloServlet), and then execute its service () method. The service() method is defined in the parent class HttpServlet, it will call the doGet() or doPost() method, and then output the data ("Hello world") to the web page.

Let's see now what the service() function of HttpServlet looks like.

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 {
        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);
    }
}

From the above code, we can see that the service() method of HttpServlet is a template method, which implements the execution process of the entire HTTP request. doGet() and doPost() are parts of the template that can be customized by subclasses. In fact, this is equivalent to the Servlet framework providing an extension point (doGet(), doPost() method), allowing framework users to embed business code into the framework through the extension point without modifying the source code of the Servlet framework.

2.JUnit TestCase

Similar to Java Servlet, the JUnit framework also provides some functional extension points (setUp(), tearDown(), etc.) through the template mode, allowing framework users to extend functions on these extension points.

When using the JUnit test framework to write unit tests, the test classes we write must inherit the TestCase class provided by the framework. In the TestCase class, the runBare() function is a template method, which defines the overall process of executing the test case: first execute setUp() to do some preparatory work, then execute runTest() to run the real test code, and finally execute tearDown() to do Finishing work.

The specific code of the TestCase class is as follows. Although setUp() and tearDown() are not abstract functions, they also provide default implementations and do not force subclasses to reimplement, but this part can also be customized in subclasses, so it also conforms to the definition of template mode.

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 {
  }
}

key review

Well, that's all for today's content. Let's summarize and review together, what you need to focus on.

The template method pattern defines an algorithm skeleton in a method and defers certain steps to subclass implementation. The template method pattern allows subclasses to redefine certain steps in an algorithm without changing the overall structure of the algorithm. The "algorithm" here can be understood as "business logic" in a broad sense, and does not specifically refer to the "algorithm" in data structures and algorithms. The algorithm skeleton here is the "template", and the method containing the algorithm skeleton is the "template method", which is also the origin of the name of the template method pattern.

In the classic implementation of the template pattern, the template method is defined as final to avoid being rewritten by subclasses. Methods that need to be rewritten by subclasses are defined as abstract, and subclasses can be forced to implement them. However, in actual project development, the implementation of the template mode is more flexible, and the above two points are not necessary.

Template mode has two functions: reuse and extension. Among them, reuse means that all subclasses can reuse the code of the template method provided in the parent class. Extension means that the framework provides functional extension points through the template mode, allowing framework users to customize the functions of the framework based on the extension points without modifying the source code of the framework.

class disscussion

Assume that a certain class in a framework exposes two template methods and defines a bunch of abstract methods for the template methods to call. The code example is as follows. In project development, even if we only use one of the template methods of this class, we still have to implement all the abstract methods in the subclass, which is equivalent to invalid labor. Is there any other way to solve this problem?

public abstract class AbstractClass {
  public final void templateMethod1() {
    //...
    method1();
    //...
    method2();
    //...
  }

  public final void templateMethod2() {
    //...
    method3();
    //...
    method4();
    //...
  }

  protected abstract void method1();
  protected abstract void method2();
  protected abstract void method3();
  protected abstract void method4();
}

Welcome to leave a message and share your thoughts with me. If you gain something, you are welcome to share this article with your friends.

Guess you like

Origin blog.csdn.net/fegus/article/details/130498831