Design mode - template mode

Let's learn another behavioral design pattern, template pattern. We have repeatedly stressed that the principle and implementation of most design patterns are very simple. The difficulty is to master the application scenarios and find out what problems can be solved. Template mode is no exception. Template pattern is mainly used to solve the two problems of reuse and expansion. Today, we will explain these two functions in detail with four examples: Java Servlet, JUnit TestCase, Java InputStream and Java AbstractList.

Principle and implementation of template mode

Template pattern, the full name is Template Method Design Pattern, and English is Template Method Design Pattern. In GoF's book design patterns, 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.
  • Template method pattern defines an algorithm skeleton in a method and postpones some steps to subclasses. The template method pattern allows subclasses to redefine some steps in the 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 "template", and the method containing the algorithm skeleton is "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 an example code, as shown below. The templateMethod() function is defined as final to avoid subclasses overriding it. method1() and method2() are defined as abstract to force subclasses to implement. However, these are not necessary. In actual project development, the code implementation of template mode is relatively flexible. We will have a specific embodiment when we talk about application scenarios later.

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

Demo case - making soybean milk

Prepare the procedure for making soybean milk as follows:

  • Process for making soybean milk material selection - > adding ingredients - > soaking - > smashing in soybean milk machine
  • Different flavors of soybean milk can be made by adding different ingredients
  • The steps of selecting materials, soaking and smashing in the soybean milk machine are the same for making each flavor of soybean milk
  • Please use the template method mode (Note: because the template method mode is relatively simple, it is easy to think of this scheme, so you can use it directly,

The traditional scheme is no longer used to export the template method pattern

SoyaMilk [abstract class]

//Abstract class representing soybean milk
public abstract class SoyaMilk {

   //Template methods, make, and template methods can be made final without subclass coverage
   final void make() {
      
      select(); 
      if(customerWantCondiments()) {
         addCondiments();
      }
      soak();
      beat();
      
   }
   
   //Material selection
   void select() {
      System.out.println("Step 1: choose good fresh soybeans  ");
   }
   
   //Add different ingredients, abstract methods and subclasses
   abstract void addCondiments();
   
   //soak
   void soak() {
      System.out.println("Third, soybeans and ingredients begin to soak, which takes 3 hours ");
   }
    
   void beat() {
      System.out.println("Step 4: put the soybeans and ingredients into the soybean milk machine to break them  ");
   }
   
   //Hook method to determine whether ingredients need to be added
   boolean customerWantCondiments() {
      return true;
   }
}

RedBeanSoyaMilk

public class RedBeanSoyaMilk extends SoyaMilk {

  @Override
  void addCondiments() {
    System.out.println(" Add good red beans ");
  }
}

PureSoyaMilk

// Pure soybean milk
public class PureSoyaMilk extends SoyaMilk {

  @Override
  void addCondiments() {
    // Empty implementation
  }

  @Override
  boolean customerWantCondiments() {
    return false;
  }
}

PeanutSoyaMilk

public class PeanutSoyaMilk extends SoyaMilk {

  @Override
  void addCondiments() {
    // TODO Auto-generated method stub
    System.out.println(" Add good peanuts ");
  }
}

Client

public class Client {

  public static void main(String[] args) {
    System.out.println("----Making red bean milk----");
    SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
    redBeanSoyaMilk.make();

    System.out.println("----Making peanut soybean milk----");
    SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
    peanutSoyaMilk.make();

    System.out.println("----Making pure soybean milk----");
    SoyaMilk pureSoyaMilk = new PureSoyaMilk();
    pureSoyaMilk.make();
  }
}

Template mode function 1: reuse

At the beginning, we mentioned that the template pattern has two functions: reuse and extension.
Let's look at its first function: reuse.
The template pattern abstracts the invariant process in an algorithm into the template method templateMethod() of the parent class, and leaves the variable parts method1() and method2() to the child classes ContreteClass1 and ContreteClass2 for implementation.
All subclasses can reuse the process code defined by the template method in the parent class.

Let's experience it more intuitively through two small examples.

Java InputStream

In the Java IO class library, template patterns are used in the design of many classes, such as InputStream, OutputStream, Reader and Writer. Let's take InputStream as an example.

I pasted some relevant codes of InputStream below. In the code, the read() function is a template method, which defines the whole 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.

import java.io.IOException;

public abstract class InputStream implements Closeable {
    //... omit other code

    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 {
    //... omit other code

    @Override
    public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }
}

Java AbstractList

In the Java AbstractList class, the addAll() function can be regarded as a template method, and add() is a method that needs to be overridden by a subclass. Although it is not declared abstract, the function implementation directly throws an unsupported operationexception. The premise is that it cannot be used if the subclass is not overridden.

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

Function 2 of template mode: extension

The second function of the template pattern is extension. The extension mentioned here does not refer to the extensibility of the code, but to the extensibility of the framework, which is somewhat similar to the control inversion we mentioned earlier.

Based on this function, template mode is often used in framework development, so that framework users can customize the functions of the framework without modifying the framework source code. Let's explain it through two examples: Junit TestCase and Java Servlet.

Java Servlet

For Java Web project development, the commonly used development framework is spring MVC. 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 high-level frameworks to develop web projects, we will inevitably use servlets. In fact, it is not difficult to use the underlying servlets to develop web projects. We just need to Define a class that inherits HttpServlet, and override the doGet() or doPost() methods to handle get and post requests respectively. The specific code examples are 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 make the following configuration in the configuration file web.xml.
When Servlet containers such as Tomcat and Jetty are started, they will automatically load the mapping relationship between the URL in the configuration file and the Servlet.

<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 web address in the browser (for example, http://127.0.0.1:8080/hello )The Servlet container will receive the corresponding request, find the corresponding Servlet (HelloServlet) according to the mapping relationship between URL and Servlet, and then execute its service() method. The service() method is defined in the parent HttpServlet, and it will call doGet() Or doPost() method, and then output the data ("Hello world") to the web page.

Let's now look at 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 whole HTTP request execution process. doGet() and doPost() are the parts of the template that can be customized by subclasses. In fact, this is equivalent to that the Servlet framework provides an extension point (doGet() and doPost() methods) , let the framework users embed the business code into the framework through extension points without modifying the Servlet framework source code.

JUnit TestCase

  • Similar to Java Servlet, JUnit framework also provides some function extension points (setUp(), tearDown()) through template mode, so that framework users can extend functions on these extension points.

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

  • The specific code of TestCase class is as follows. Although setUp() and tearDown() are not abstract functions, they also provide a default implementation and do not force subclasses to re implement them, this part can also be customized in subclasses, so it also conforms to the definition of template pattern.

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.
   *
   * <p>* This method is called before a test is executed.
   */
  protected void setUp() throws Exception {}

  /**
   * * Tears down the fixture, for example, close a network connection.
   *
   * <p>* This method is called after a test is executed.
   */
  protected void tearDown() throws Exception {}
}

Principle analysis of callback

Reuse and extension are two major functions of the template pattern. In fact, there is another technical concept that can also play the same role as the template pattern, that is, Callback. Today, let's take a look at the principle, implementation and application of Callback, as well as the difference and relationship between Callback and template mode.

Compared with ordinary function calls, callback is a two-way calling relationship. Class a registers a function f to class B in advance. When class a calls the P function of class B, class B calls the F function registered by class A. The F function here is the "callback function". A calls B, and B in turn calls A. this calling mechanism is called "callback".

How does class A pass callback functions to class B? Different programming languages have different implementation methods. C language can use function pointers, while Java needs to use class objects wrapped with callback functions, which are called callback objects for short. Here I use the Java language as an example. The code is as follows:

public interface ICallback {
  void methodToCallback();
}

public class BClass {

  public void process(ICallback callback) {
    // ...
    callback.methodToCallback();
    // ...
  }
}

public class AClass {

  public static void main(String[] args) {
    BClass b = new BClass();
    b.process(
        new ICallback() { // callback object 
          @Override
          public void methodToCallback() {
            System.out.println("Call back me.");
          }
        });
  }
}

  • The above is a typical code implementation of callback in Java language. From the code implementation, we can see that the callback, like the template pattern, also has the functions of reuse and extension. Except for the callback function, the logic in the process () function of BClass class can be reused. If the ICallback and BClass classes are framework code and AClass is the client code using the framework, we can customize the process() function through ICallback, that is, the framework has the ability to expand.

  • In fact, callbacks can be used not only in code design, but also in higher-level architecture design. For example, the payment function is realized through the third-party payment system. After initiating the payment request, the user will not block until the payment result is returned, but register the callback interface (similar to the callback function, generally a URL for callback) to the third-party payment system. After the execution of the third-party payment system, the result will be returned to the user through the callback interface.

  • Callbacks can be divided into synchronous callbacks and asynchronous callbacks (or delayed callbacks). Synchronous callback refers to executing the callback function before the function returns; Asynchronous callback refers to the execution of the callback function after the function returns. The above code is actually the implementation of synchronous callback. The callback function methodToCallback() is executed before the process() function returns. The above payment example is the implementation of asynchronous callback. After initiating payment, you do not need to wait for the callback interface to be called to return directly. From the perspective of application scenarios, synchronous callbacks look more like template mode and asynchronous callbacks look more like observer mode.

Application example 1: JdbcTemplate

Spring provides many Template classes, such as JdbcTemplate, RedisTemplate and RestTemplate. Although they are called xxtemplate, they are not implemented based on the Template pattern, but based on callbacks. To be exact, they should be synchronous callbacks. The synchronous callback is very similar to the Template mode from the application scenario. Therefore, in terms of naming, these classes use the word Template as the suffix.

The design ideas of these Template classes are very similar, so we only take the JdbcTemplate as an example. For other Template classes, you can read the source code and analyze it yourself.

In the previous chapters, we also mentioned many times that Java provides JDBC class libraries to encapsulate different types of database operations. However, it is a little complicated to write the code to operate the database directly using JDBC. For example, the following paragraph is the code to query user information using JDBC.

public class JdbcDemo {
  public User queryUser(long id) {
    Connection conn = null;
    Statement stmt = null;
    try {
      // 1. Load drive
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo", "xzg", "xzg");
      // 2. Create a statement class object to execute SQL statements
      stmt = conn.createStatement();
      // 3. The resultset class is used to store the obtained result set
      String sql = "select * from user where id=" + id;
      ResultSet resultSet = stmt.executeQuery(sql);
      String eid = null, ename = null, price = null;
      while (resultSet.next()) {
        User user = new User();
        user.setId(resultSet.getLong("id"));
        user.setName(resultSet.getString("name"));
        user.setTelephone(resultSet.getString("telephone"));
        return user;
      }
    } catch (ClassNotFoundException e) {
      // TODO: log...
    } catch (SQLException e) {
      // TODO: log...
    } finally {
      if (conn != null)
        try {
          conn.close();
        } catch (SQLException e) {
          // TODO: log...
        }
      if (stmt != null)
        try {
          stmt.close();
        } catch (SQLException e) {
          // TODO: log...
        }
    }
    return null;
  }
}

The queryUser() function contains a lot of process code, which has nothing to do with business. For example, load the driver, create a database connection, create a statement, close the connection, close the statement, and handle exceptions. For different SQL execution requests, these process codes are the same and can be reused. We don't need to repeat them every time.

To solve this problem, Spring provides JdbcTemplate to further encapsulate JDBC to simplify database programming. To query User information using JdbcTemplate, we only need to write code related to this business, including the SQL statement of the query User and the mapping relationship between the query result and the User object. Other process codes are encapsulated in the JdbcTemplate class, so we don't need to rewrite them every time. I rewrite the above example with JdbcTemplate. The code is much simpler, as shown below:

public class JdbcTemplateDemo {
  private JdbcTemplate jdbcTemplate;

  public User queryUser(long id) {
    String sql = "select * from user where id=" + id;
    return jdbcTemplate.query(sql, new UserRowMapper()).get(0);
  }

  class UserRowMapper implements RowMapper<User> {
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
      User user = new User();
      user.setId(rs.getLong("id"));
      user.setName(rs.getString("name"));
      user.setTelephone(rs.getString("telephone"));
      return user;
    }
  }
}

How to implement the bottom layer of JdbcTemplate? Let's take a look at its source code. Because there are many JdbcTemplate codes, I only excerpted some relevant codes and posted them below. Among them, the JdbcTemplate pulls out the unchanged execution process through the callback mechanism, puts it into the template method execute(), and designs the variable part as a callback StatementCallback, which can be customized by the user. The query() function is a secondary encapsulation of the execute() function, making the interface more convenient to use.

@Override
  public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
    return query(sql, new RowMapperResultSetExtractor<T>(rowMapper));
  }

  @Override
  public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
    Assert.notNull(sql, "SQL must not be null");
    Assert.notNull(rse, "ResultSetExtractor must not be null");

    if (logger.isDebugEnabled()) {
      logger.debug("Executing SQL query [" + sql + "]");
    }

    class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
      @Override
      public T doInStatement(Statement stmt) throws SQLException {
        ResultSet rs = null;

        try {
          rs = stmt.executeQuery(sql);
          ResultSet rsToUse = rs;
          if (nativeJdbcExtractor != null) {
            rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
          }
          return rse.extractData(rsToUse);
        } finally {
          JdbcUtils.closeResultSet(rs);
        }
      }

      @Override
      public String getSql() {
        return sql;
      }
    }
    return execute(new QueryStatementCallback());
  }

  @Override
  public <T> T execute(StatementCallback<T> action) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");
    Connection con = DataSourceUtils.getConnection(getDataSource());
    Statement stmt = null;

    try {
      Connection conToUse = con;
      if (this.nativeJdbcExtractor != null
          && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
        conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
      }
      stmt = conToUse.createStatement();
      applyStatementSettings(stmt);
      Statement stmtToUse = stmt;
      if (this.nativeJdbcExtractor != null) {
        stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
      }
      T result = action.doInStatement(stmtToUse);
      handleWarnings(stmt);
      return result;
    } catch (SQLException ex) {
      // Release Connection early, to avoid potential connection pool deadlock
      // in the case when the exception translator hasn't been initialized yet.
      JdbcUtils.closeStatement(stmt);
      stmt = null;
      DataSourceUtils.releaseConnection(con, getDataSource());
      con = null;
      throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
    } finally {
      JdbcUtils.closeStatement(stmt);
      DataSourceUtils.releaseConnection(con, getDataSource());
    }
  }

Application example 2: setClickListener()

In client development, we often register event listeners for controls. For example, the following code registers listeners for click events of Button controls in Android application development.

Button button = (Button)findViewById(R.id.button);
   button.setOnClickListener(new OnClickListener() {
   @Override
   public  void  onClick(View v)  {
     System.out.println("I am clicked.");
   }
 });

In terms of code structure, event listeners are much like callbacks, that is, passing an object containing a callback function (onClick()) to another function. From the perspective of application scenario, it is very similar to the observer mode, that is, register the observer in advance, send the click event to the observer when the user clicks the button, and execute the corresponding onclick () function.

As we mentioned earlier, callbacks are divided into synchronous callbacks and asynchronous callbacks. The callback here is asynchronous. After registering the callback function in setOnClickListener(), we don't need to wait for the callback function to execute. This also confirms that asynchronous callbacks are more like observer mode as we mentioned earlier.

Application example 3: addshutdown hook ()

  • Hook can be translated into "hook". What's the difference between hook and Callback?

  • Some people on the internet think Hook is callback. They say the same thing, but they express different things. Some people think Hook is an application of callback. Callback focuses more on the description of syntax mechanism, and Hook focuses more on the description of application scenarios. I personally agree with the latter statement. However, this is not important. We just need to know the code and use it in the scene.

  • The classic application scenario of Hook is the shutdown hook of Tomcat and JVM. Next, let's take JVM as an example. The JVM provides the runtime. Addshutdown Hook (thread Hook) method, which can register a Hook closed by the JVM. When the application closes, the JVM will automatically call the Hook code. The code example is as follows:

public class ShutdownHookDemo {
  private static class ShutdownHook extends Thread {
    public void run() {
      System.out.println("I am called during shutting down.");
    }
  }

  public static void main(String[] args) {
    Runtime.getRuntime().addShutdownHook(new ShutdownHook());
  }
}

Let's look at the code implementation of addshutdown hook (), as shown below. Here I only give some relevant codes.

public class Runtime {
  public void addShutdownHook(Thread hook) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
      sm.checkPermission(new RuntimePermission("shutdownHooks"));
    }
    ApplicationShutdownHooks.add(hook);
  }
}

class ApplicationShutdownHooks {
    /* The set of registered hooks */
    private static IdentityHashMap<Thread, Thread> hooks;
    static {
        try {
            Shutdown.add(1 /* shutdown hook invocation order */,
                false /* not registered if shutdown in progress */,
                new Runnable() {
                    public void run() {
                        runHooks();
                    }
                }
            );
            hooks = new IdentityHashMap<>();
        } catch (IllegalStateException e) {
            // application shutdown hooks cannot be added if
            // shutdown is in progress.
            hooks = null;
        }
    }


    private ApplicationShutdownHooks() {}

    /* Add a new shutdown hook.  Checks the shutdown state and the hook itself,
     * but does not do any security checks.
     */
    static synchronized void add(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook.isAlive())
            throw new IllegalArgumentException("Hook already running");

        if (hooks.containsKey(hook))
            throw new IllegalArgumentException("Hook previously registered");

        hooks.put(hook, hook);
    }

    /* Remove a previously-registered hook.  Like the add method, this method
     * does not do any security checks.
     */
    static synchronized boolean remove(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook == null)
            throw new NullPointerException();

        return hooks.remove(hook) != null;
    }

    /* Iterates over all application hooks creating a new thread for each
     * to run in. Hooks are run concurrently and this method waits for
     * them to finish.
     */
    static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }

        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            while (true) {
                try {
                    hook.join();
                    break;
                } catch (InterruptedException ignored) {
                }
            }
        }
    }
}

From the code, we can find that the logic about hooks is encapsulated in the ApplicationShutdownHooks class. When the application closes, the JVM will call the runHooks() method of this class to create multiple threads and execute multiple hooks concurrently. After registering the Hook, we don't need to wait for the Hook execution to complete, so this is also an asynchronous callback.

Template mode VS callback

  • The principle, implementation and application of callback are finished here. Next, let's compare the template pattern and callback from the perspective of application scenario and code implementation.

  • From the application scenario, the synchronous callback is almost consistent with the template mode. They are all in a large algorithm skeleton, free to replace one of the steps, for the purpose of code reuse and expansion. Asynchronous callback is quite different from template mode, which is more like observer mode.

  • In terms of code implementation, callback and template patterns are completely different. Callbacks are implemented based on composite relationships. Passing one object to another is a relationship between objects; The template pattern is implemented based on the inheritance relationship. The subclass overrides the abstract method of the parent class, which is a relationship between classes.

  • As we mentioned earlier, composition is better than inheritance. In fact, here is no exception. In terms of code implementation, callback is more flexible than template mode, which is mainly reflected in the following points.

  • For a language like Java that only supports single inheritance, subclasses written based on template mode have inherited a parent class and no longer have the ability to inherit.

  • Callbacks can use anonymous classes to create callback objects without defining classes in advance; The template pattern defines different subclasses for different implementations.

  • If multiple template methods are defined in a class, and each method has a corresponding abstract method, even if we only use one of the template methods, the subclass must implement all the abstract methods. The callback is more flexible. We only need to inject the callback object into the template method used.

Reprint

Author: youthlql
Article link: https://imlql.cn/post/dd09051e.html
Copyright notice: unless otherwise stated, all articles on this blog adopt CC BY-NC-SA 4.0 license agreement. Reprint please indicate the time record from Youth!

Keywords: Java Design Pattern

Added by control on Sun, 19 Sep 2021 01:42:55 +0300