Template mode
Introduction
This is a very simple model, but it is widely used. It is simple because only inheritance relationships are used in this pattern.
Due to its own defects, inheritance relationship has been labeled as "evil" by experts. "Use delegation relationship instead of inheritance relationship", "try to use interface implementation instead of abstract class inheritance" and other expert warnings, let us rookies "look at inheritance differently".
In fact, inheritance still has many advantages. But the disadvantages of being abused by everyone seem to be more obvious. Rational use of inheritance can still play a good role in your system design. The template method pattern is one of the examples.
Definition and structure
Template method pattern: defines the skeleton of the algorithm in an operation, and delays some steps to subclasses. The subclass can redefine some specific steps of an algorithm without changing the structure of the algorithm. The structure of the algorithm here can be understood as the business process you design according to your needs. Specific steps refer to those links that may have variables in content.
It can be seen that the template method mode is also designed to skillfully solve the impact of changes on the system. The template method is used to enhance the scalability of the system and minimize the impact of changes on the system. This can be clearly seen in the following examples.
Let's look at the structure of this simple pattern:
-
Abstract class: defines one or more abstract methods for specific subclasses to implement; Moreover, a template method should be implemented to define the skeleton of an algorithm. The template method can not only call the previous abstract method, but also call other operations, as long as it can complete its own mission.
-
Concrete class: implement the abstract methods in the parent class to complete the steps related to specific subclasses in the algorithm.
The following is the structure diagram of the template method pattern. Directly take the drawing on the design pattern and use it:
give an example
Let's find an example in JUnit, which I just analyzed the source code. TestCase in JUnit and its subclasses are examples of template method patterns. Set the whole test process in the abstract class TestCase. For example, execute the Setup method to initialize the test premise, run the test method, and then TearDown to cancel the test setting. But what will you do in Setup and TearDown? Who knows!! Therefore, the specific implementation of these steps is delayed to the subclass, that is, the test class you implement.
Take a look at the relevant source code.
This is the template method for executing tests in TestCase. As you can see, as mentioned in the previous definition, it formulates the framework of "algorithm" - first execute the setUp method for initialization, then execute the test method, and finally execute tearDown to release the resources you get.
public void runBare() throws Throwable { setUp(); try { runTest(); } finally { tearDown(); } }
These are the two methods used above. Different from the definition, these two methods are not implemented as abstract methods, but two empty inaction methods (called hook methods). This is because in the test, we do not have to let the test program use these two methods to initialize and release resources. If it is an abstract method, subclasses must give it an implementation, whether it is used or not. This is obviously unreasonable. Using hook methods, you can override these methods in subclasses when you need them.
protected void setUp() throws Exception { } protected void tearDown() throws Exception { }
Application
According to the above analysis of definitions and the description of examples, it can be seen that the template method is applicable to the following situations:
-
Implement the invariant part of an algorithm at one time, and leave the variable behavior to subclasses.
-
The common behaviors in each subclass should be extracted and concentrated in a common parent class to avoid code duplication. In fact, this can be said to be a good coding habit.
-
Control subclass extension. Template methods only call operations at specific points, which allows extension only at these points. For example, the runBare() method above only applies the setUp method in front of runTest. If you don't want subclasses to modify the framework defined by your template methods, you can do it in two ways: one is not to reflect your template methods in the API; Or set your template method to final.
It can be seen that the common behavior of code can be extracted by using the template method pattern to achieve the purpose of reuse. Moreover, in the template method pattern, the template method of the parent class controls the specific implementation in the child class. In this way, you don't need to know much about business processes when implementing subclasses.
import java.util.*; import junit.framework.*; //Concrete Visitor interface Visitor { void visit(Gladiolus g); void visit(Runuculus r); void visit(Chrysanthemum c); } // The Flower hierarchy cannot be changed: //Element role interface Flower { void accept(Visitor v); } //The following three specific element roles class Gladiolus implements Flower {public void accept(Visitor v) { v.visit(this);} } class Runuculus implements Flower { public void accept(Visitor v) { v.visit(this);} } class Chrysanthemum implements Flower { public void accept(Visitor v) { v.visit(this);} } // Add the ability to produce a string: //Implementation specific visitor roles class StringVal implements Visitor { String s; public String toString() { return s; } public void visit(Gladiolus g) { s = "Gladiolus"; } public void visit(Runuculus r) { s = "Runuculus"; } public void visit(Chrysanthemum c) { s = "Chrysanthemum"; } } // Add the ability to do "Bee" activities: //Another specific visitor role class Bee implements Visitor { public void visit(Gladiolus g) { System.out.println("Bee and Gladiolus"); } public void visit(Runuculus r) { System.out.println("Bee and Runuculus"); } public void visit(Chrysanthemum c) { System.out.println("Bee and Chrysanthemum"); } } //This is an object generator //This is not a complete object structure, but only simulates the elements in the object structure class FlowerGenerator { private static Random rand = new Random(); public static Flower newFlower() { switch(rand.nextInt(3)) { default: case 0: return new Gladiolus(); case 1: return new Runuculus(); case 2: return new Chrysanthemum(); } } } //Customer test procedure public class BeeAndFlowers extends TestCase { /* Here you can see the process executed by visitor mode: First, get a specific visitor role on the client Traversal object structure Call the accept method on each element to pass in the specific visitor role This completes the whole process */ //Object structure roles are assembled here List flowers = new ArrayList(); public BeeAndFlowers() { for(int i = 0; i < 10; i++) flowers.add(FlowerGenerator.newFlower()); } Visitor sval ; public void test() { // It's almost as if I had a function to// produce a Flower string representation: //This place you can modify to use another specific visitor role sval = new StringVal(); Iterator it = flowers.iterator(); while(it.hasNext()) { ((Flower)it.next()).accept(sval); System.out.println(sval); } } public static void main(String args[]) { junit.textui.TestRunner.run(BeeAndFlowers.class); } }
Double Dispatch
By the way, did you realize the implementation of double dispatch in the above example?
First, pass the concrete visitor pattern as a parameter to the concrete element role in the client program (highlighted). This completes an assignment.
After entering the specific element role, the specific element role calls the visitor method in the specific visitor pattern as a parameter, and passes itself (this) as a parameter. The specific visitor mode can be executed by selecting methods according to different parameters (as shown in the highlighted place). This completes the second assignment.
Advantages, disadvantages and Application
Let's first look at whether the use of visitor mode can avoid the pain in the introduction. After using the visitor mode, for adding new operations to the original class hierarchy, you only need to implement a specific visitor role without modifying the whole class hierarchy. And this meets the requirements of the "opening and closing principle". Moreover, each specific visitor role corresponds to a related operation. Therefore, if the requirements of an operation change, only one specific visitor role can be modified without changing the whole class hierarchy.
It seems that the visitor model can really solve some of the problems we face.
Moreover, because the visitor pattern provides an additional layer of "visitors" for our system, we can add some additional operations on element roles to the visitors.
However, the principle of "opening and closing" is always one-sided. If the class hierarchy in the system changes, what impact will it have on the visitor pattern? You must modify the visitor role and each specific visitor role
It seems that the visitor role is not suitable for situations where the roles of specific elements often change. Moreover, if the visitor role wants to perform operations related to the element role, it must let the element role expose its internal attributes, which means that other objects can also be accessed in java. This destroys the encapsulation of element roles. Moreover, in visitor mode, the information that can be transmitted between elements and visitors is limited, which often limits the use of visitor mode.
The book "design patterns" gives the application of visitor patterns:
-
An object structure contains many class objects with different interfaces, and you want to perform some operations on these objects that depend on their specific classes.
-
You need to perform many different and irrelevant operations on objects in an object structure, and you want to avoid these operations "polluting" the class Visitor of these objects, so that you can centralize the relevant operations and define them in one class.
-
When the object structure is shared by many applications, use the Visitor mode to make each application contain only the operations that need to be used.
-
The class that defines the object structure rarely changes, but it is often necessary to define new operations on this structure. Changing the object structure class requires redefining the interface to all visitors, which can be costly. If object structure classes change frequently, it may be better to define these operations in these classes. Can you understand it well?
summary
This is a clever and complex model, and its use conditions are relatively harsh. When there is a fixed data structure in the system (such as the class hierarchy above)
With different behaviors, visitor mode may be a good choice.