The visitor pattern of Java and patterns

Source: Internet
Author: User

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

Concept of the assignment

  The type of the variable being declared is called the static type of the variable, and some people call the static type the explicit type (apparent type), whereas the real type of the object referenced by the variable is called the actual type of the variable (Actual type). Like what:

List List = Null;list = new ArrayList ();

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

The choice of the method according to the type of the object is the allocation (Dispatch), and the allocation (Dispatch) is divided into two kinds, namely static dispatch and dynamic dispatch .

  static allocation (static Dispatch) occurs at compile time, and allocations occur based on static type information. Static dispatch is no stranger to us, and method overloading is static dispatch.

  Dynamic dispatch (Dispatch) occurs at run time, and dynamic dispatch dynamically displaces a method.

Static Dispatch

Java supports static dispatch through method overloading. Mozi rode the story as an example, MO can ride a white horse or dark horse. The class diagrams of Mozi and the White Horse, the Black Horse and the Horse are as follows:

In this system, Mozi is represented by the Mozi class

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 black 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 made up of three method overloads. These three methods accept the parameters of Horse (Horse), White Horse (Whitehorse), Black Horse (Blackhorse) and so on respectively.

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

Why is it? Two calls to the ride () method pass in different parameters, that is, WH and BH. Although they have different true types, their static types are the same, all of which are horse types.

The dispatch of an overloaded method is based on a static type, and the dispatch process is completed at compile time.

Dynamic Dispatch

Java supports dynamic dispatch through rewriting of methods. Using the horse grazing story as an example, the code looks like this:

public class Horse {public        void Eat () {        System.out.println ("equine Grazing");}    }
public class Blackhorse extends Horse {        @Override public    void Eat () {        System.out.println ("Black Horse eats Grass");}    }
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, then the print is "dark horse grazing"; Conversely, if the Eat () method above calls the horse () method of the Eat class, then the print is "horse grazing".

So, at the heart of the problem is that the Java compiler does not always know which code will be executed at compile time, because the compiler knows only the static type of the object, not the actual type of the object, and the invocation of the method is based on the actual type of the object, not the static type. In this way, the Eat () method of the last line above calls the Eat () method of the Blackhorse class, which prints "dark horse grazing".

Type of dispatch

The object that a method belongs to is called the receiver of the method, and the method's receiver and the method's parameters are collectively known as the method's volume. Like the test class in the example below

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 parameter, str, and its type is string.

The object-oriented language can be divided into single-Dispatch languages (Uni-dispatch) and multi-Dispatch languages (Multi-dispatch), depending on how many kinds of parcels can be allocated. The single-Dispatch language chooses the method based on the type of a volume, and the multi-Dispatch language chooses the method based on the type of the more than one volume.

Both C + + and Java are single-Dispatch languages, and examples of multi-tasking languages include Clos and Cecil. According to this distinction, Java is a dynamic single-Dispatch language , because the dynamic allocation of the language only takes into account the type of receiver of the method, but also the static multi-Dispatch language , Because this language's 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: The first is the name of the request, but the actual type of the receiver. A single dispatch restricts the process of selecting a method so that only one volume can be taken into account, which is usually the recipient of the method. In the Java language, if an operation is acting on an object of unknown type, then the real type test of the object will only happen once, which is the characteristic of the dynamic single dispatch.

Double allocation

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

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

In the figure there are two objects, the left side is called West, the right side is called East. Now the West object first calls the Goeast () method of the East object and passes it to itself. When the East object is called, 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 turned over to two objects, and its timing diagram is as follows:

In this way, two method calls are made, and the control is passed to the East object by the West object, which is then passed to the West object, just like a pass by two objects.

But the mere return of the ball does not solve the problem of double allocation. The key is how to make use of these two calls, as well as the dynamic single-Dispatch function of the Java language, so that two single dispatches can be triggered during this pass.

Dynamic single dispatch occurs in the Java language 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 shown in:

Source

West class

Public abstract class West {public        abstract void GoWest1 (SubEast1 East);        public abstract void GoWest2 (SubEast2 East);}

SubWest1 class

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

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

Public abstract class East {public    abstract void Goeast (West West);

SubEast1 class

public class SubEast1 extends east{    @Override public    void Goeast (West West) {        west.gowest1 (this);    } Public        String myName1 () {        return ' SubEast1 ';    }}

SubEast2 class

public class SubEast2 extends east{    @Override public    void Goeast (West West) {        west.gowest2 (this);    }        Public String myName2 () {        return ' SubEast2 ';    }}

Client class

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 operation results are as follows

SubWest1 + SubEast1
SubWest2 + SubEast1

When the system is running, the SubWest1 and SubEast1 objects are created first, and then the client calls the SubEast1 Goeast () method and passes the SubWest1 object in. Because the SubEast1 object overrides its Goeast () method of the superclass east, a dynamic single dispatch occurs at this time. When the SubEast1 object is called, it gets the SubWest1 object from the argument, so it immediately calls 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 performed at this time.

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

Since these two names are one from the east hierarchy and the other from the West hierarchy, 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 indeterminate data structures, which frees up the coupling between the structure and the operation of the structure, allowing the set of operations to evolve relatively freely. A brief picture of the visitor pattern is as follows:

Each node of the data structure can accept a call from a visitor that passes in the node object to the visitor object, while the visitor object performs the operation of the node object in turn. Such a process is called "double allocation". The node invokes the visitor, passes it on itself, and the visitor executes an algorithm against the node. The schematic class diagram for the visitor pattern is as follows:

  

The visitor pattern involves the following roles:

Abstract Visitor (Visitor) role: declares one or more method actions that form the interface that all specific visitor roles must implement.

Specific Visitor (concretevisitor) Role: implements the interface declared by the abstract visitor, which is the individual access actions declared by the abstract visitor.

abstract node Role: declares an accept operation that accepts a visitor object as a parameter.

specific node (concretenode) Role: implements the accepted operation specified by the abstract node.

struct Object (objectstructure) role: as a responsibility, you can traverse all the elements in the structure, and if necessary, provide a high-level interface that allows the visitor object to access each element, and if necessary, can be designed as a composite object or a aggregation, such as list or set.

Source

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

Public interface Visitor {    /**     * Corresponds to NodeA access operation *    /public void visit (NodeA node);    /**     * Corresponds to NodeB's access operation     *    /public void visit (NodeB node);}

Specific visitor Visitora class

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

Specific visitor Visitorb class

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

Abstract Node class

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

Specific node class NodeA

public class NodeA extends node{    /**     * Accept Operation     *    /@Override public    void Accept (Visitor Visitor) {        Visitor.visit (this);    }    /**     * NodeA Unique method *    /public String Operationa () {        return ' NodeA ';    }}

Specific node class NodeB

public class NodeB extends node{    /**     * Accept Method     *    /@Override public    void Accept (Visitor Visitor) {        Visitor.visit (this);    }    /**     * NodeB Unique method *    /public String operationb () {        return ' NodeB ';    }}

The structure object role class, which holds a cluster and provides an add () method to the outside world as a management operation for aggregation. By calling this method, you can dynamically add a new node.

public class Objectstructure {        private list<node> nodes = new arraylist<node> ();        /**     * Execute method Action */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

public class Client {public    static void Main (string[] args) {        //Create a struct object        objectstructure os = new OBJECTST Ructure ();        Add a node        os.add (New NodeA ()) to the structure;        Add a node        os.add (New NodeB ()) to the structure;        Create a visitor        Visitor Visitor = new Visitora ();        Os.action (visitor);    }}

Although there is no complex object tree structure with multiple tree nodes in this schematic implementation, the visitor pattern is often used to handle complex object tree structures in the real 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.

The client then invokes the Fabric object aggregation management method, adding the NodeA and NodeB nodes to the structure object.

Finally, the client invokes the action method of the Structure object action (), which initiates the access process.

  

Access process sequence diagram

  

The structure object iterates through all the nodes in its own saved cluster, and 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 acceptance method of the NodeA object is called, and the Visitora object itself is passed in;

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

(3) The Visitora object calls the unique method of the NodeA object Operationa ().

Thus the double dispatch process is completed, and then the NodeB is accessed, and the process of the visit is the same as the process of NodeA being accessed, which is no longer described here.

Advantages of the visitor pattern

Good Extensibility

The ability to add new functionality to elements in an object structure without modifying the elements in the object structure.

Good reusability

You can improve the reusability by defining the functionality common to the entire object structure through the visitor.

Detach unrelated behavior

Visitors can separate unrelated behaviors, encapsulate related behaviors together, and form a visitor, so that each visitor's functionality is relatively single.

Disadvantages of the visitor pattern

object structure changes are difficult

Does not apply to cases in which the class in the object structure changes frequently, because the structure of the object changes, the interface of the visitor and the realization of the visitor are changed correspondingly, the cost is too high.

Damage Encapsulation

Visitor patterns often require an object structure to open internal data to visitors and Objectstructrue, which destroys the encapsulation of objects.

The visitor pattern of Java and patterns

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.