Home >Java >javaTutorial >Java implements sample code sharing using reflection in visitor mode

Java implements sample code sharing using reflection in visitor mode

黄舟
黄舟Original
2017-03-23 11:13:331287browse

The collection type is in Oriented to ObjectsProgramming is very commonly used, which also brings some code-related questions, such as, "How to operate different types of objects in the collection? ”

One approach is to iterate through each element in the collection and then perform specific operations based on its type. This will be very complicated, especially when you don’t know the type of the elements in the collection. If y wants To print the elements in the collection, you can write a method like this:

public void messyPrintCollection(Collection collection) {
    Iterator iterator = collection.iterator()
    while (iterator.hasNext())
        System.out.println(iterator.next().toString())
}

It seems simple. It just calls the Object.toString() method and prints out the object, right? But what if your collection is a What about vectors containing hashtables? That gets more complicated. You have to check the type of the collection return object:

public void messyPrintCollection(Collection collection) {
    Iterator iterator = collection.iterator()
    while (iterator.hasNext()) {
        Object o = iterator.next();
        if (o instanceof Collection)
            messyPrintCollection((Collection)o);
        else
            System.out.println(o.toString());
        }
}

OK, now you can handle the characters returned by other objects. String is not what you want? If you want to add quotes to the string object and add an f after the Float object, what should you do?

public void messyPrintCollection(Collection collection) {
    Iterator iterator = collection.iterator()
    while (iterator.hasNext()) {
        Object o = iterator.next();
        if (o instanceof Collection)
            messyPrintCollection((Collection)o);
        else if (o instanceof String)
            System.out.println("'"+o.toString()+"'");
        else if (o instanceof Float)
            System.out.println(o.toString()+"f");
        else
            System.out.println(o.toString());
    }
}

The code becomes cluttered quickly. You don’t want the code to contain a lot of if-else statements! How to avoid it? Visitor pattern can help you. ##To implement the visitor pattern, you need to create a Visitor

interface

and create a Visitable interface for the visited collection object. Next, you need to create a specific class to implement the Visitor and Visitable interfaces. It is roughly as follows:

public interface Visitor
{
    public void visitCollection(Collection collection);
    public void visitString(String string);
    public void visitFloat(Float float);
}
public interface Visitable
{
    public void accept(Visitor visitor);
}
For a specific String class, it can be implemented like this:

public class VisitableString implements Visitable
{
    private String value;
    public VisitableString(String string) {
        value = string;
    }
    public void accept(Visitor visitor) {
        visitor.visitString(this);
    }
}

In the accept method, according to different types, call the corresponding method in the visitor:

visitor.visitString(this)

The specific implementation of Visitor is as follows:

public class PrintVisitor implements Visitor
{
    public void visitCollection(Collection collection) {
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            Object o = iterator.next();
            if (o instanceof Visitable)
                ((Visitable)o).accept(this);
    }
    public void visitString(String string) {
        System.out.println("'"+string+"'");
    }
    public void visitFloat(Float float) {
        System.out.println(float.toString()+"f");
    }
}

At that time, as long as the VisitableFloat class and VisitableCollection class are implemented and the appropriate visitor method is called , you can get rid of the messyPrintCollection method that contains a bunch of if-else structures, and implement the same function in a very refreshing way. The visitCollection() method calls Visitable.accept(this), and the accept() method does the opposite. The correct method in the visitor is called. This is double dispatch: Visitor calls a method in the Visitable class, which in turn calls a method in the Visitor class.

Although after implementing visitor, if-else The statement is gone, but it still introduces a lot of additional code. It's annoying that you have to wrap the original objects, String and Float, into a class that implements the Visitable interface. You can restrict the visited collection to only contain Visitable objects.

However, there is still a lot of additional work to be done. Even worse, what do you do when you want to add a new Visitable type, such as VisitableInteger? This is a major drawback of the visitor pattern. If you want to add a new Visitable type, you have to change the Visitor interface and every class that implements the Visitor interface methods. You can not design Visitor as an interface. Instead, you can design Visitor as an abstract base class with

No operation

. This is very similar to the Adapter class in Java GUI. The problem with doing this is that you will exhaust the single inheritance, and a common situation is that you also want to use inheritance to implement other functions, such as inheriting the StringWriter class. This again can only successfully access objects that implement the Visitable interface. Fortunately, Java can make your visitor pattern more flexible, and you can add Visitable objects as you wish. How to achieve it? The answer is to use reflection. The ReflectiveVisitor interface using reflection only requires one method:

public interface ReflectiveVisitor {
    public void visit(Object o);
}

Okay, the above is very simple. The Visitable interface will not be used for now, I will talk about it later. Now, I implement PrintVisitor class using reflection.

public class PrintVisitor implements ReflectiveVisitor {
    public void visitCollection(Collection collection)
    { ... same as above ... }
    public void visitString(String string)
    { ... same as above ... }
    public void visitFloat(Float float)
    { ... same as above ... }
    public void default(Object o)
    {
        System.out.println(o.toString());
    }
    public void visit(Object o) {
        // Class.getName() returns package information as well.
        // This strips off the package information giving us
        // just the class name
        String methodName = o.getClass().getName();
        methodName = "visit"+
                    methodName.substring(methodName.lastIndexOf('.')+1);
        // Now we try to invoke the method visit<methodName>
        try {
            // Get the method visitFoo(Foo foo)
            Method m = getClass().getMethod(methodName,
                new Class[] { o.getClass() });
            // Try to invoke visitFoo(Foo foo)
            m.invoke(this, new Object[] { o });
        } catch (NoSuchMethodException e) {
            // No method, so do the default implementation
            default(o);
        }
    }
}

Now you don’t need to use the Visitable wrapper class (which wraps the original types String and Float). You can access visit() directly and it will call the correct method. One advantage of visit() is that it dispatches the method it sees fit. This doesn't necessarily use reflection, it could use a completely different mechanism.

In the new PrintVisitor class, there are operation methods corresponding to Collections, String and Float; for types that cannot be processed, they can be captured through catch statements. For types that cannot be handled, you can try to handle all their superclasses by extending the visit() method. First, add a new method getMethod(Class c), and the return value is a method that can be triggered. It will

search

all parent classes and interfaces of Class c to find a matching method.

protected Method getMethod(Class c) {
    Class newc = c;
    Method m = null;
    // Try the superclasses
    while (m == null && newc != Object.class) {
        String method = newc.getName();
        method = "visit" + method.substring(method.lastIndexOf(&#39;.&#39;) + 1);
        try {
            m = getClass().getMethod(method, new Class[] {newc});
        } catch (NoSuchMethodException e) {
            newc = newc.getSuperclass();
        }
    }
    // Try the interfaces.  If necessary, you
    // can sort them first to define &#39;visitable&#39; interface wins
    // in case an object implements more than one.
    if (newc == Object.class) {
        Class[] interfaces = c.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
            String method = interfaces[i].getName();
            method = "visit" + method.substring(method.lastIndexOf(&#39;.&#39;) + 1);
            try {
                m = getClass().getMethod(method, new Class[] {interfaces[i]});
            } catch (NoSuchMethodException e) {}
        }
    }
    if (m == null) {
        try {
            m = thisclass.getMethod("visitObject", new Class[] {Object.class});
        } catch (Exception e) {
            // Can&#39;t happen
        }
    }
    return m;
}

这看上去很复杂,实际上并不。大致来说,首先根据传入的class名称搜索可用方法;如果没找到,就尝试从父类搜索;如果还没找到,就从接口中尝试。最后,(仍没找到)可以使用visitObject()作为默认方法。

由于大家对传统的访问者模式比较熟悉,这里沿用了之前方法命名的惯例。但是,有些人可能注意到,把所有的方法都命名为“visit”并通过参数类型不同来区分,这样更高效。然而,如果你这么做,你必须把visit(Object o)方法的名称改为其他,比如dispatch(Object o)。否则,(当没有对应处理方法时),你无法退回到默认的处理方法,并且当你调用visit(Object o)方法时,为了确保正确的方法调用,你必须将参数强制转化为Object。

为了利用getMethod()方法,现在需要修改一下visit()方法。

public void visit(Object object) {
    try {
        Method method = getMethod(getClass(), object.getClass());
        method.invoke(this, new Object[] {object});
    } catch (Exception e) { }
}

现在,visitor类更加强大了——可以传入任意的对象并且有对应的处理方法。另外,有一个默认处理方法,visitObject(Object o),的好处就是就可以捕捉到任何没有明确说明的类型。再稍微修改下,你甚至可以添加一个visitNull()方法。

我仍保留Visitable接口是有原因的。传统访问者模式的另一个好处是它可以通过Visitable对象控制对象结构的遍历顺序。举例来说,假如有一个实现了Visitable接口的类TreeNode,它在accept()方法中遍历自己的左右节点。

public void accept(Visitor visitor) {
    visitor.visitTreeNode(this);
    visitor.visitTreeNode(leftsubtree);
    visitor.visitTreeNode(rightsubtree);
}

这样,只要修改下Visitor类,就可以通过Visitable类控制遍历:

public void visit(Object object) throws Exception
{
    Method method = getMethod(getClass(), object.getClass());
    method.invoke(this, new Object[] {object});
    if (object instanceof Visitable)
    {
        callAccept((Visitable) object);
    }
}
public void callAccept(Visitable visitable) {
    visitable.accept(this);
}

如果你实现了Visitable对象的结构,你可以保持callAccept()不变,就可以使用Visitable控制的对象遍历。如果你想在visitor中遍历对象结构,你只需重写allAccept()方法,让它什么都不做。

当使用几个不同的visitor去操作同一个对象集合时,访问者模式的力量就会展现出来。比如,当前有一个解释器、中序遍历器、后续遍历器、XML编写器以及SQL编写器,它们可以处理同一个对象集合。我可以轻松地为这个集合再写一个先序遍历器或者一个SOAP编写器。另外,它们可以很好地兼容它们不识别的类型,或者我愿意的话可以让它们抛出异常。

总结

使用Java反射,可以使访问者模式提供一种更加强大的方式操作对象结构,可以按照需求灵活地增加新的Visitable类型。我希望在你的编程之旅中可以使用访问者模式。

The above is the detailed content of Java implements sample code sharing using reflection in visitor mode. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn