[Template method pattern] of design patterns, template method and functional callback, which one is the trend?

1. What is the template method pattern

Template Method Pattern (Template Method Pattern) is also called template pattern, refers to 定义一个操作中的算法的框架,而将一些步骤延迟到子类中. It allows subclasses to redefine some specific steps of the algorithm without changing the structure of the algorithm, which belongs to the behavioral design pattern.

The template method pattern actually encapsulates a fixed process, which consists of several steps. The specific steps can be implemented differently by subclasses, so that the fixed process can produce different results. It is very simple, in fact, it is the inheritance mechanism of classes, but it is a very widely used pattern. The essence of the target method pattern is to abstract the encapsulation process and implement it concretely.

For example, going to a bank to handle business generally needs to go through the following four processes: taking a number, queuing up, handling specific businesses, and rating bank staff, among which the business of taking a number, queuing up, and rating bank staff is important for each customer. The same can be implemented in the parent class, but the specific business is different from person to person. It may be deposits, withdrawals or transfers, etc., which can be delayed to subclasses.

1. Main role

insert image description here

The Template Method pattern contains the following main roles:

  • Abstract Class: Responsible for giving the outline and skeleton of an algorithm. It consists of a template method and several basic methods.
  • Concrete Class: Implement the abstract methods and hook methods defined in the abstract class, which are the steps of a top-level logic.

The abstract class contains template methods and basic methods, 模板方法defines the skeleton of the algorithm, and calls the basic methods it contains in a certain order. 基本方法It is a method to realize each step of the algorithm, and it is an integral part of the template method. There are three basic methods:

  • Abstract Method: An abstract method is declared by an abstract class and implemented by its concrete subclasses.
  • Concrete Method: A concrete method is declared and implemented by an abstract class or a concrete class, and its subclasses can be overwritten or directly inherited.
  • Hook Method: It has been implemented in the abstract class, including logical methods for judgment and empty methods that need to be rewritten by subclasses. Generally, the hook method is a logical method for judging. The name of this type of method is generally isXxx, and the return value type is boolean type or int type.

2. Application scenarios

1. Implement the invariant part of an algorithm at one time, and leave the variable behavior to subclasses.
2. The common behaviors in each subclass are extracted and concentrated into a common parent class, so as to avoid code duplication.
3. It is necessary to determine whether a certain step in the algorithm of the parent class is executed through the subclass, so as to realize the reverse control of the subclass over the parent class.

3. Advantages and disadvantages

advantage:

  • Improve code reusability, put the same part of the code in the abstract parent class, and put different codes in different subclasses.
  • Realized 控制反转, through a parent class to call the operation of its subclasses, through the specific implementation of the subclasses to expand different behaviors, the reverse control is realized, and it conforms to the "opening and closing principle".

shortcoming:

  • For each different implementation, a subclass needs to be defined, which will lead to an increase in the number of classes, a larger system, and a more abstract design.
  • The abstract method in the parent class is implemented by the subclass, and the execution result of the subclass will affect the result of the parent class, which leads to a reverse control structure, which increases the difficulty of code reading.
  • The disadvantage of the inheritance relationship itself is that if the parent class adds a new abstract method, all subclasses must be changed again.

4. Precautions and details

  • The basic idea of ​​the template method pattern is that the algorithm only exists in one place, that is, in the parent class, and it is easy to modify. When you need to modify the algorithm, you only need to modify the template method of the parent class or some steps that have been implemented, and the subclass will inherit these modifications.
  • Achieved maximum code reuse. The template method of the parent class and some steps that have been implemented will be inherited by the subclass and used directly.
  • It not only unifies the algorithm, but also provides great flexibility. The template method of the parent class ensures that the structure of the algorithm remains unchanged, while the implementation of some steps is provided by the subclass.
  • The disadvantage of this mode: each different implementation requires a subclass implementation, which leads to an increase in the number of classes and makes the system even larger.
  • Generally, template methods are added with the final keyword to prevent subclasses from overriding template methods.
  • Template method pattern usage scenario: When a process is to be completed, the process needs to perform a series of steps. This series of steps is basically the same, but the individual steps may be different in implementation. Usually, it is considered to use the template method pattern for processing.

Two, examples

1. Stir-fry case

The steps of cooking are fixed, divided into steps such as pouring oil, heating oil, pouring vegetables, pouring seasonings, and stir-frying. It is now simulated in code through the template method pattern. The class diagram is as follows:
insert image description here

// 抽象模板类
public abstract class AbstractClass {
    
    
	public final void cookProcess() {
    
    
		//第一步:倒油
		this.pourOil();
		//第二步:热油
		this.heatOil();
		//第三步:倒蔬菜
		this.pourVegetable();
		//第四步:倒调味料
		this.pourSauce();
		//第五步:翻炒
		this.fry();
	}
	// 第一步都是一样的,倒油
	public void pourOil() {
    
    
		System.out.println("倒油");
	}
	//第二步:热油是一样的,所以直接实现
	public void heatOil() {
    
    
		System.out.println("热油");
	}
	//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
	public abstract void pourVegetable();
	//第四步:倒调味料是不一样
	public abstract void pourSauce();
	//第五步:翻炒是一样的,所以直接实现
	public void fry(){
    
    
		System.out.println("炒啊炒啊炒到熟啊");
	}
}
// 炒包菜
public class ConcreteClass_BaoCai extends AbstractClass {
    
    
	@Override
	public void pourVegetable() {
    
    
		System.out.println("下锅的蔬菜是包菜");
	}
	@Override
	public void pourSauce() {
    
    
		System.out.println("下锅的酱料是辣椒");
	}
}
// 炒菜心
public class ConcreteClass_CaiXin extends AbstractClass {
    
    
	@Override
	public void pourVegetable() {
    
    
		System.out.println("下锅的蔬菜是菜心");
	}
	@Override
	public void pourSauce() {
    
    
		System.out.println("下锅的酱料是蒜蓉");
	}
}
// 测试类
public class Client {
    
    
	public static void main(String[] args) {
    
    
		//炒手撕包菜
		ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
		baoCai.cookProcess();
		//炒蒜蓉菜心
		ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
		caiXin.cookProcess();
	}
}

注意:为防止恶意操作,一般模板方法都加上 final 关键词。

(1) Hook method of template method pattern

In the parent class of the template method pattern, we can define a method that does nothing by default, and subclasses can override it as appropriate. This method is called a "hook".

The main purpose of the hook method is to intervene in the execution process, making our control behavior process more flexible and more in line with actual business needs. The hook method is generally a return value (such as boolean, int, etc.) suitable for conditional branch statements. We can decide whether to use the hook method according to our business scenarios.

Let's still take cooking as an example, and add a step to the template abstract class: blanch. Not all fried dishes need to be blanched:

// 抽象模板类
public abstract class AbstractClass {
    
    
	public final void cookProcess() {
    
    
		if(this.needBlanching()) {
    
    
			// 按需要判断是否要焯水
			this.blanching();
		}
		//第一步:倒油
		this.pourOil();
		//第二步:热油
		this.heatOil();
		//第三步:倒蔬菜
		this.pourVegetable();
		//第四步:倒调味料
		this.pourSauce();
		//第五步:翻炒
		this.fry();
	}
	public boolean needBlanching() {
    
    
		return false;
	}
	public void blanching() {
    
    
		System.out.println("焯水");
	}
	// 第一步都是一样的,倒油
	public void pourOil() {
    
    
		System.out.println("倒油");
	}
	//第二步:热油是一样的,所以直接实现
	public void heatOil() {
    
    
		System.out.println("热油");
	}
	//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
	public abstract void pourVegetable();
	//第四步:倒调味料是不一样
	public abstract void pourSauce();
	//第五步:翻炒是一样的,所以直接实现
	public void fry(){
    
    
		System.out.println("炒啊炒啊炒到熟啊");
	}
}
// 炒包菜
public class ConcreteClass_BaoCai extends AbstractClass {
    
    
	@Override
	public boolean needBlanching() {
    
    
		System.out.println("下锅的蔬菜是包菜,需要焯水");
		return true;
	}
	@Override
	public void pourVegetable() {
    
    
		System.out.println("下锅的蔬菜是包菜");
	}
	@Override
	public void pourSauce() {
    
    
		System.out.println("下锅的酱料是辣椒");
	}
}
// 炒菜心
public class ConcreteClass_CaiXin extends AbstractClass {
    
    
	@Override
	public void pourVegetable() {
    
    
		System.out.println("下锅的蔬菜是菜心");
	}
	@Override
	public void pourSauce() {
    
    
		System.out.println("下锅的酱料是蒜蓉");
	}
}
// 测试类
public class Client {
    
    
	public static void main(String[] args) {
    
    
		//炒手撕包菜
		ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
		baoCai.cookProcess();
		//炒蒜蓉菜心
		ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
		caiXin.cookProcess();
	}
}

2. Refactor JDBC case

Create a template class JdbcTemplate to encapsulate all JDBC operations.

public abstract class JdbcTemplate {
    
    
    private DataSource dataSource;

    public JdbcTemplate2(DataSource dataSource) {
    
    
        this.dataSource = dataSource;
    }

    public final List<?> executeQuery(String sql, Object[] values){
    
    
        try {
    
    
            //1、获取连接
            Connection conn = this.getConnection();
            //2、创建语句集
            PreparedStatement pstm = this.createPrepareStatement(conn,sql);
            //3、执行语句集
            ResultSet rs = this.executeQuery(pstm,values);
            //4、处理结果集
            List<?> result = this.parseResultSet(rs);
            //5、关闭结果集
            rs.close();
            //6、关闭语句集
            pstm.close();
            //7、关闭连接
            conn.close();
            return result;
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return null;
    }

    private List<?> parseResultSet(ResultSet rs) throws Exception {
    
    
        List<Object> result = new ArrayList<Object>();
        int rowNum = 0;
        while (rs.next()){
    
    
            result.add(this.mapRow(rs,rowNum++));
        }
        return result;
    }
	// 抽象方法
    public abstract Object mapRow(ResultSet rs,int rowNum) throws Exception;


    private ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
    
    
        for (int i = 0; i < values.length; i++) {
    
    
            pstm.setObject(i,values[i]);
        }
        return pstm.executeQuery();
    }

    private PreparedStatement createPrepareStatement(Connection conn, String sql) throws SQLException {
    
    
        return conn.prepareStatement(sql);
    }

    private Connection getConnection() throws SQLException {
    
    
        return this.dataSource.getConnection();
    }
}

public class MemberDao extends JdbcTemplate {
    
    
    public MemberDao2(DataSource dataSource) {
    
    
        super(dataSource);
    }

    public Object mapRow(ResultSet rs, int rowNum) throws Exception {
    
    
        Member member = new Member();
        //字段过多,原型模式
        member.setUsername(rs.getString("username"));
        member.setPassword(rs.getString("password"));
        member.setAge(rs.getInt("age"));
        member.setAddr(rs.getString("addr"));
        return member;
    }

    public List<?> selectAll(){
    
    
        String sql = "select * from t_member";
        return super.executeQuery(sql ,null);
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        MemberDao memberDao = new MemberDao(new DataSource());
        List<?> result = memberDao.selectAll();
    }
}

We use the template method to encapsulate jdbc, and rewrite the data mapping method in the subclass, which greatly improves the code reuse rate.

However, is this solution really the best solution?

3. Template method mode and Callback callback mode

The template mode is often used in framework development. By providing function extension points, framework users can customize the functions of the framework based on the extension points without modifying the source code of the framework. In addition, the template mode can also play a role in code reuse.

Reuse and extension are the two major functions of template mode. In fact, there is another technical concept that can also play the same role as template mode, that is 回调(Callback).

1. The basic principle of callback

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

// A 类将回调函数传递给 B 类
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() {
    
     //回调对象
      @Override
      public void methodToCallback() {
    
    
        System.out.println("Call back me.");
      }
    });
  }
}

Callbacks can be used not only in code design, but also in higher-level architecture design. For example, the payment function is implemented through a three-party payment system. After the user initiates a payment request, it generally does not block until the payment result is returned, but registers a callback interface (similar to a callback function, generally a callback URL) to the three-party payment system. , after the execution of the three-party payment system is completed, 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 executing the callback function after the function returns. The above code is actually the implementation of synchronous callback. Before the process() function returns, the callback function methodToCallback() is executed. The payment example above is an implementation of asynchronous callback. After the payment is initiated, it returns directly without waiting for the callback interface to be called. From the perspective of application scenarios, synchronous callbacks look more like template mode, and asynchronous callbacks look more like observer mode.

2. Case 1: Refactoring JDBC in callback mode

Above we use the template method mode to use JDBC, and on this basis, we further use combination and callback methods to refactor.

Define the JdbcTemplate tool class:

public class JdbcTemplate {
    
    
    private DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
    
    
        this.dataSource = dataSource;
    }

    public final List<?> executeQuery(String sql,RowMapper<?> rowMapper,Object[] values){
    
    
        try {
    
    
            //1、获取连接
            Connection conn = this.getConnection();
            //2、创建语句集
            PreparedStatement pstm = this.createPrepareStatement(conn,sql);
            //3、执行语句集
            ResultSet rs = this.executeQuery(pstm,values);
            //4、处理结果集
            List<?> result = this.parseResultSet(rs,rowMapper);
            //5、关闭结果集
            rs.close();
            //6、关闭语句集
            pstm.close();
            //7、关闭连接
            conn.close();
            return result;
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return null;
    }

    private List<?> parseResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {
    
    
        List<Object> result = new ArrayList<Object>();
        int rowNum = 0;
        while (rs.next()){
    
    
            result.add(rowMapper.mapRow(rs,rowNum++));
        }
        return result;
    }


    private ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
    
    
        for (int i = 0; i < values.length; i++) {
    
    
            pstm.setObject(i,values[i]);
        }
        return pstm.executeQuery();
    }

    private PreparedStatement createPrepareStatement(Connection conn, String sql) throws SQLException {
    
    
        return conn.prepareStatement(sql);
    }

    private Connection getConnection() throws SQLException {
    
    
        return this.dataSource.getConnection();
    }
}

public class MemberDao {
    
    
    private JdbcTemplate jdbcTemplate = new JdbcTemplate(null);

    public List<?> selectAll(){
    
    
        String sql = "select * from t_member";
        return jdbcTemplate.executeQuery(sql, new RowMapper<Member>() {
    
    
            public Member mapRow(ResultSet rs, int rowNum) throws Exception {
    
    
                Member member = new Member();
                //字段过多,原型模式
                member.setUsername(rs.getString("username"));
                member.setPassword(rs.getString("password"));
                member.setAge(rs.getInt("age"));
                member.setAddr(rs.getString("addr"));
                return member;
            }
        },null);
    }
}

We found that the original inheritance mechanism was cancelled, and the combination + method callback mechanism was used, which seemed to make the code more flexible.

3. Case 2: Register to monitor events

In client development, we often register event listeners for controls. For example, the following code is to register a listener for the click event 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.");
  }
});

From the perspective of code structure, event listeners are very similar to callbacks, that is, passing an object containing a callback function (onClick()) to another function. From the perspective of the application scenario, it is very similar to the observer mode, that is, the observer (OnClickListener) is registered in advance. When the user clicks the button, the click event is sent to the observer and the corresponding onClick() function is executed.

4. Template Method Pattern VS Callback

From the perspective of application scenarios, the synchronous callback is almost the same as the template mode. They are all in a large algorithm skeleton, free to replace a certain step in it, and achieve the purpose of code reuse and expansion. The asynchronous callback is quite different from the template mode, more like the observer mode.

From the perspective of code implementation, the callback and template patterns are completely different. 回调基于组合关系来实现, passing one object to another object is a relationship between objects; 模板模式基于继承关系来实现, a subclass overrides the abstract method of the parent class, which is a relationship between classes.

Composition is better than inheritance. In fact, there is no exception here. In terms of code implementation, the callback is more flexible than the template mode, which is mainly reflected in the following points:

  • In a language like Java that only supports single inheritance, a subclass written based on the template pattern has inherited a parent class and no longer has the ability to inherit.
  • Callbacks can use anonymous classes to create callback objects without having to define classes in advance; while template patterns define different subclasses for different implementations.
  • If multiple template methods are defined in a class, and each method has a corresponding abstract method, then 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.

4. The target method pattern in the source code

1. InputStream class

The InputStream class uses the template method pattern. Multiple read() methods are defined in the InputStream class, as follows:

public abstract class InputStream implements Closeable {
    
    
	//抽象方法,要求子类必须重写
	public abstract int read() throws IOException;
	
	public int read(byte b[]) throws IOException {
    
    
		return read(b, 0, b.length);
	}
	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(); //调用了无参的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;
	}
}

As can be seen from the above code, the read() method without parameters is an abstract method, which must be implemented by subclasses. The read(byte b[]) method calls the read(byte b[], int off, int len) method, so the method to focus on here is the method with three parameters.

The method of reading a byte array data has been defined in the InputStream parent class is to read one byte at a time, store it in the first index position of the array, and read len bytes of data. Specifically how to read a byte of data is implemented by subclasses.

2、AbstractList

Let's take a look at part of the source code of AbstractList:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    
    
	public abstract E get(int index);
}

The get method is an abstract method, and its logic is implemented by subclasses. ArrayList is a subclass of AbstractList.

In the same way, if there is AbstractList, there will be AbstractSet and AbstractMap, and the source code also uses the template method pattern.

Guess you like

Origin blog.csdn.net/A_art_xiang/article/details/130699189