The Java design pattern visitor pattern Detailed _java

Source: Internet
Author: User
Tags data structures

In Dr Shanhong's "Java and Schema" book, this describes the visitor (Visitor) pattern:

The visitor pattern is the behavior pattern of the object. The purpose of the visitor pattern is to encapsulate actions that are applied to some data structure element. Once these operations need to be modified, the data structure that accepts the operation can remain unchanged.

The concept of assignment

When a variable is declared, the type is called the static type of the variable (static type), and some people call the static type the obvious type (apparent type), while the actual type of the object referenced by the variable is called the real type of the variable (Actual type). Like what:

Copy Code code as follows:

List List = null;
List = new ArrayList ();

Declares a list of variables, its static type (also known as the obvious type) is a list, and its actual type is ArrayList.

According to the type of object, the choice of the method is to assign (Dispatch) and assign (Dispatch) into two kinds, namely static dispatch and dynamic dispatch.

Static dispatch (Dispatch) occurs at compile time, and the allocation occurs based on static type information. Static dispatch is not unfamiliar to us, method overload is static dispatch.

Dynamic Dispatch occurs at run time, and dynamic dispatch replaces a method dynamically.

Static Dispatch

Java supports static allocations through method overloading. With the story of Mozi riding, as an example, Mozi can ride a white horse or a dark horse. The class diagram of Mozi and White Horse, black Horse and horse is shown below:

In this system, Mozi is represented by the Mozi class

Copy Code code as follows:

public class Mozi {

public void Ride (horse h) {
System.out.println ("Horseback riding");
}

public void Ride (Whitehorse wh) {
System.out.println ("Riding white Horse");
}

public void Ride (Blackhorse BH) {
System.out.println ("Riding a Dark Horse");
}

public static void Main (string[] args) {
Horse WH = new Whitehorse ();
Horse BH = new Blackhorse ();
Mozi Mozi = new Mozi ();
Mozi.ride (WH);
Mozi.ride (BH);
}

}

Obviously, the ride () method of the Mozi class is a three-method overload. These three methods accept the parameters of the Horse (horse), White Horse (Whitehorse), Dark Horse (Blackhorse) and the like.

So what does the program print out at run time? The result is that the program prints out the same two lines of "horseback riding." In other words, Mozi found that all he rode was horses.

Why, then? Two calls to the ride () method are passed in different parameters, namely WH and BH. Although they have different real types, their static types are the same, and they are all horse types.

The allocation of overloaded methods is based on static types, and the dispatch process is completed at compile time.

Dynamic Dispatch

Java supports dynamic dispatch by overriding the methods. The story of eating grass with horses as an example, the code looks like this:

Copy Code code as follows:

public class Horse {

public void Eat () {
System.out.println ("Horse eats grass");
}
}

Copy Code code as follows:

public class Blackhorse extends horse {

@Override
public void Eat () {
System.out.println ("Black Horse eats Grass");
}
}

Copy Code code as follows:

public class Client {

public static void Main (string[] args) {
Horse h = new Blackhorse ();
H.eat ();
}

}

The static type of the variable h is horse, and the real type is blackhorse. If the Eat () method of the last line above calls the Eat () method of the Blackhorse class, so the top print is "black horse grazing"; Conversely, if the Eat () method above calls the Eat () method of the horse class, then the "Horse Eats Grass" is printed.

So the core of the problem is that the Java compiler does not always know at compile time which code will be executed, because the compiler only knows the object's static type, not the object's true type, and the method's invocation is based on the object's true type, not the static type. As a result, the last line of the Eat () method calls the Eat () method of the Blackhorse class, which prints "dark horse grazing."

Type of assignment

The object to which a method belongs is called the receiver of the method, and the receiver of the method and the parameter of the method are collectively referred to as the number of methods. For example, the test class in the following example

Copy Code code as follows:

public class Test {

public void print (String str) {
System.out.println (str);
}
}

In the above class, the print () method belongs to the test object, so its receiver is the test object. The print () method has one argument that is STR and its type is string.

The object-oriented language can be divided into single assignment languages (Uni-dispatch) and multiple assignment languages (multi-dispatch), depending on how many kinds of volume the allocation can be based on. The single assignment language chooses the method according to the type of the volume, and the multiple assignment language chooses the method according to the type of more than one volume.

C + + and Java are single allocation languages, and examples of multiple assignment languages include Clos and Cecil. According to this distinction, Java is a dynamic single assignment language because the dynamic dispatch of this language takes into account only the type of receiver of the method and the static, multiple-assign language, because the allocation of overloaded methods takes into account the type of receiver of the method and the type of all parameters of the method.

In a language that supports dynamic single dispatch, there are two conditions that determine which action a request will invoke: One is the name of the request, but the true type of the receiver. A single assignment restricts the process of selecting a method so that only one volume can be taken into account, and this volume is usually the recipient of the method. In the Java language, if an action is applied to an object of unknown type, the true type test for that object only happens once, which is the characteristic of the dynamic single assignment.

Double allocation

A method determines the execution of different code based on the type of two volumes, which is "double dispatch." The Java language does not support dynamic multiple allocations, which means that Java does not support dynamic dual allocation. However, by using design patterns, dynamic double dispatch can also be implemented in the Java language.

In Java, two method calls can be used to achieve the goal of two allocations. The class diagram looks like this:

There are two objects in the picture, west on the left and east on the right. Now the West object first invokes the Goeast () method of the East object and passes it itself. When the East object is invoked, it immediately knows who the caller is based on the parameters passed in, and in turn invokes the Gowest () method of the caller object. With two calls, the program control is handed over to two objects, and the sequence diagram looks like this:

There were two method calls, which were passed to the East object by two objects like passing, and then returned to the West object.

But the mere return of the ball does not solve the problem of double dispatch. The key is how to make use of these two calls, and the dynamic single dispatch function of the Java language, which can trigger the two-time single dispatch during this passing process.

Dynamic Dispatch In the Java language occurs when a subclass overrides a method of a parent class. In other words, both West and east must be in their own type hierarchy, as the following illustration shows:

Source

West class

Copy Code code as follows:

Public abstract class West {

public abstract void GoWest1 (SubEast1 East);

public abstract void GoWest2 (SubEast2 East);
}

SubWest1 class

Copy Code code as follows:

public class SubWest1 extends west{

@Override
public void GoWest1 (SubEast1 East) {

System.out.println ("SubWest1 +" + east.myname1 ());
}

@Override
public void GoWest2 (SubEast2 East) {

System.out.println ("SubWest1 +" + east.myname2 ());
}
}

SubWest2 class

Copy Code code as follows:

public class SubWest2 extends west{
@Override
public void GoWest1 (SubEast1 East) {

System.out.println ("SubWest2 +" + east.myname1 ());
}

@Override
public void GoWest2 (SubEast2 East) {

System.out.println ("SubWest2 +" + east.myname2 ());
}
}

East Class

Copy Code code as follows:

Public abstract class East {

public abstract void Goeast (West West);
}


SubEast1 class
Copy Code code as follows:

public class SubEast1 extends east{
@Override
public void Goeast (West West) {
West.gowest1 (this);
}

Public String myName1 () {
return "SubEast1";
}
}

SubEast2 class

Copy Code code as follows:

public class SubEast2 extends east{
@Override
public void Goeast (West West) {
West.gowest2 (this);
}

Public String myName2 () {
return "SubEast2";
}
}

Client class

Copy Code code as follows:

public class Client {

public static void Main (string[] args) {
Combination 1
East East = new SubEast1 ();
West West = new SubWest1 ();
East.goeast (west);
Combination 2
East = new SubEast1 ();
West = new SubWest2 ();
East.goeast (west);
}

}

The results of the operation are as follows

Copy Code code as follows:

SubWest1 + SubEast1
SubWest2 + SubEast1

When the system runs, the SubWest1 and SubEast1 objects are created first, and then the client invokes the SubEast1 Goeast () method and passes the SubWest1 object in. Because the SubEast1 object rewrites the Goeast () method of its superclass east, a dynamic single assignment occurs at this time. When the SubEast1 object is called, the SubWest1 object is obtained from the parameter, so it immediately invokes the GoWest1 () method of the object and passes itself in. Because the SubEast1 object has the right to choose which object to invoke, a dynamic method assignment is made at this time.

This time the SubWest1 object gets the SubEast1 object. By invoking the object MyName1 () method, you can print out your name and the name of the Subeast object, and the sequence diagram looks like this:

Since these two names come from the east hierarchy, the other comes from the West hierarchy, so their combination is dynamically determined. This is the implementation mechanism of dynamic double dispatch.

Structure of the visitor pattern

The visitor pattern is suitable for systems with relatively undetermined data structures, which frees up the coupling between the structure and the operations acting on the structure, so that the set of operations can evolve relatively freely. A thumbnail of the visitor pattern looks like this:

Each node of the data structure can accept a call from a visitor, which passes in the node object to the Visitor object, which in turn performs the operation of the Node object. Such a process is called "Double dispatch". The node invokes the visitor, passing it in itself, and the visitor executes an algorithm against the node. The schematic class diagram of the visitor pattern is shown below:

The roles involved in the visitor pattern are as follows:

Abstract Visitor (Visitor) role : Declares one or more method actions to form the interfaces that all specific visitor roles must implement.
A specific visitor (concretevisitor) role : Implements an interface declared by an abstract visitor, which is the individual access actions that an abstract visitor declares.
abstract node Role : Declares an accept operation that accepts a visitor object as a parameter.
specific node (concretenode) Role : Implements the accepted operation as specified by the abstract node.
struct Object (objectstructure) role : It is the responsibility to traverse all elements of the structure and, if necessary, to provide a high-level interface to allow the visitor object to access each element, or, if necessary, to design a composite object or a clustered such as list or set.

Source

As you can see, the abstract visitor role prepares an access operation for each specific node. Because there are two nodes, there are two access operations corresponding to them.

Copy Code code as follows:

Public interface Visitor {
/**
* corresponding to the NodeA access operation
*/
public void Visit (NodeA node);
/**
* corresponding to the NodeB access operation
*/
public void Visit (NodeB node);
}

Specific visitors Visitora class

Copy Code code as follows:

public class Visitora implements Visitor {
/**
* corresponding to the NodeA access operation
*/
@Override
public void Visit (NodeA node) {
System.out.println (Node.operationa ());
}
/**
* corresponding to the NodeB access operation
*/
@Override
public void Visit (NodeB node) {
System.out.println (NODE.OPERATIONB ());
}

}

Specific visitors Visitorb class

Copy Code code as follows:

public class Visitorb implements Visitor {
/**
* corresponding to the NodeA access operation
*/
@Override
public void Visit (NodeA node) {
System.out.println (Node.operationa ());
}
/**
* corresponding to the NodeB access operation
*/
@Override
public void Visit (NodeB node) {
System.out.println (NODE.OPERATIONB ());
}

}

Abstract Node class

Copy Code code as follows:

Public abstract class Node {
/**
* Accept Action
*/
public abstract void Accept (Visitor Visitor);
}

Specific node class NodeA

Copy Code code as follows:

public class NodeA extends node{
/**
* Accept Action
*/
@Override
public void Accept (Visitor Visitor) {
Visitor.visit (this);
}
/**
* NodeA-specific methods
*/
Public String Operationa () {
return "NodeA";
}

}

Specific node class NodeB

Copy Code code as follows:

public class NodeB extends node{
/**
* Acceptance method
*/
@Override
public void Accept (Visitor Visitor) {
Visitor.visit (this);
}
/**
* NodeB-specific methods
*/
Public String operationb () {
return "NodeB";
}
}

The structure object role class, which holds an aggregation and provides the external add () method as an administrative operation on the aggregation. By calling this method, you can dynamically add a new node.

Copy Code code as follows:

public class Objectstructure {

Private list<node> nodes = new arraylist<node> ();

/**
* Perform method actions
*/
public void Action (Visitor Visitor) {

for (Node node:nodes)
{
Node.accept (visitor);
}

}
/**
* Add a new element
*/
public void Add (node node) {
Nodes.Add (node);
}
}

Client class

Copy Code code as follows:

public class Client {

public static void Main (string[] args) {
Create a struct object
Objectstructure OS = new Objectstructure ();
Add a node to the structure
Os.add (New NodeA ());
Add a node to the structure
Os.add (New NodeB ());
Create a visitor
Visitor Visitor = new Visitora ();
Os.action (visitor);
}

}


Although there is not a complex object tree structure with multiple branch nodes in this schematic implementation, the visitor pattern is usually used to deal with complex object tree structures in the actual system, and the visitor pattern can be used to deal with tree structure problems spanning multiple hierarchical structures. This is where the visitor pattern is powerful.

Preparation process Sequence diagram

First, the schematic client creates a struct object and then passes in a new NodeA object and a new NodeB object.

Second, the client creates a Visitora object and passes this object to the struct object.

Then, the client invokes the structure object aggregation management method and joins the NodeA and NodeB nodes into the structure object.

Finally, the client invokes the action method actions () of the struct object and initiates the access procedure.

Access process sequence diagram

The structure object traverses all nodes in its own saved aggregation, in this system is the node NodeA and NodeB. First NodeA will be accessed, and this access is made up of the following operations:

(1) The NodeA object's acceptance method accept () is invoked and the Visitora object itself is passed in;

(2) The NodeA object invokes the access method of the Visitora object in turn, and the NodeA object itself is passed in;

(3) The Visitora object invokes the NodeA object's unique Method Operationa ().

This completes the double dispatch process, and then the NodeB is accessed, and the process of the visit is the same as the process of NodeA being visited, no longer described here.

The advantages of the visitor pattern

Good extensibility
enables you to add new functionality to elements in an object structure without modifying the elements in the object structure.
Good reusability
improves reusability by defining functionality that is common to the entire object structure through visitors.
Separation-independent behavior
allows visitors to isolate unrelated behaviors, encapsulate related behaviors together, and form a visitor so that each visitor has a single function. Disadvantages of the
visitor pattern
Object structure changes are difficult
does not apply to situations in which classes in the object structure often change, because the object structure changes, and the visitor's interface and the visitor's implementation change accordingly, at a high price. The
Destroy encapsulation
Visitor pattern typically requires the object structure to open internal data to visitors and objectstructrue, which undermines the encapsulation of the object.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.