Generic visitor pattern in java

2020-08-05 11:30发布

问题:

Is the following java implementation of the visitor pattern using generics, general enough to be useful? (I suppose it is).

Could it be improved in some way? It's important to be easily call-able using anonymous classes. Thanks.

(Example of use):

Vector<Number> numbers = new Vector<Number>();

        numbers.add(new Double(1.2));
        numbers.add(new Float(-1.2));
        numbers.add(new Double(4.8));
        numbers.add(new Float(-3.4));
        numbers.add(new Long(123456));
        numbers.add(new Short("14"));

        For.each(numbers, new Visitor<Number>() {
            public void doIt(Double n) {
                System.out.println("doIt() for double: " + n);
            }
            public void doIt(Float n) {
                System.out.println("doIt() for float: " + n);
            }
            public void doIt(Number n) {
                System.out.println("doIt() for Number: " + n);
            }
        });

        Visitor<Number> visi =  new Visitor<Number>() {
            private StringBuffer  all = new StringBuffer ();
            public void doIt(Number n) {
                System.out.println("doIt() for Number: " + n);
                all.append(n.toString() + " ");
            }
            public Object getResult () {
                return all;
            }
        };

        For.each(numbers, visi);

        System.out.println ("all -> " + visi.getResult());

Definitions:

//............................................
abstract class Visitor<T> {
    public void visit(T n) {
        try {
            this.getClass().getDeclaredMethod("doIt", n.getClass()).invoke(this, n);
        } catch (Exception ex) {
            doIt((T) n);
        }
    }
    public void doIt(T n) {
        System.out.println("doIt() for base " + n);
    }
    public Object getResult() {
        return null;
    }
} // class

//............................................
class For {
    public static <T> void each (Collection<T> c, Visitor<T> f) {
        for (T v : c) {
            f.visit(v);
        }
    } // ()
} // class

回答1:

This is not the Visitor Pattern.

Visitor is characterized by the visitee having an accept(Visitor v) method that interacts with an visit method in the visitor taking as parameter the visitee and overloaded for the varying type of the visitee, forming a "double dispatch" mechanism.

Quoting from the "Applicability" section for Visitor in Design Patterns:

Use the Visitor pattern when
  • an object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes.
  • many distinct and unrelated operations need to be performed on objects in an object structure, and you want to avoid "polluting" their classes with these operations. Visitor lets you keep related operations together by defining them in one class. When the object structure is shared by many applications, use Visitor to put operations in just those applications that need them.
  • the classes defining the object structure rarely change, but you often want to define new operations over the structure. Changing the object structure classes requires redefining the interface to all visitors, which is potentially costly. If the object structure classes change often, then it's probably better to define the operations in those classes.

So this pattern is for dealing with similar opertaions on objects of multiple types. In your examples the objects you're calling visitors can only deal with one type.

In your answer revising to use reflection to handle multiple types (which by the way would be better done as an edit to the question or as a separate question), you're avoiding creating an accept(Visitor v) method in the visited classes by using reflection, which is to a degree accomplishing the same goal, but somewhat awkwardly. I still would resist calling it an implementation of Visitor.

If code in the style you've written here is useful to you, by all means use it, but please don't call it a Visitor.

This is more like a Strategy Pattern or a Function Object, and if you rename the generic class in a way that reflects that, it's in fact useful, and your usage is similar to common patterns of list handling in functional languages.

What I would likely do with the code from the question is rename your Visitor<T> to Operation<T> and rename your visit(T t) to execute(T t) or apply(T t), thinking of an Operation as a Function without a return value. I have in fact used exactly this in ways similar to what you're doing, and used similar tactics for collection "mapping" using generic Function<Domain, Range> objects. I'm not sure what pattern name actually fits it, but it's not Visitor. It's bringing functional list-comprehension style to an OO world where functions are not naturally first-class objects.



回答2:

Thanks to the answer of donroby about my initial code not implementing the visitor pattern I came to this new version.

I suppose it now implements the visitor pattern without the need of modifying the visited element with an accept() method. Anyway, it is able to call the right method depending on the element type (I guess that's the mission for the accept()), thanks to reflection.

First, an example of use:

Vector<Number> numbers = new Vector<Number>();

    numbers.add(new Double(1.2));
    numbers.add(new Float(-1.2));
    numbers.add(new Double(4.8));
    numbers.add(new Float(-3.4));
    numbers.add(new Long(123456));
    numbers.add(new Short("14"));

    For.each(numbers, new Visitor<Number>() {
        public void doIt(Double n) {
            System.out.println("doIt() for double: " + n);
        }
        public void doIt(Float n) {
            System.out.println("doIt() for float: " + n);
        }
        public void doIt(Number n) {
            System.out.println("doIt() for Number: " + n);
        }
    });

That produces this output

doIt() for double: 1.2
doIt() for float: -1.2
doIt() for double: 4.8
doIt() for float: -3.4
doIt() for Number: 123456
doIt() for Number: 14

And finally the code

abstract class Visitor<T> {
public void visit(T n) {
    try {
        this.getClass().getDeclaredMethod("doIt", n.getClass()).invoke(this, n);
    } catch (Exception ex) {
        doIt((T) n);
    }
}
public void doIt(T n) {
    System.out.println("doIt() for base " + n);
}
public Object getResult() {
    return null;
}

}

class For {
public static <T> void each (Collection<T> c, Visitor<T> f) {
    for (T v : c) {
        f.visit(v);
    }
} // ()

}



回答3:

how about

    for(String s : words)
        System.out.println (s.toUpperCase());

    int total = 0;
    for(String s : words)
        total = total + s.length();
    System.out.println (" sum of lengths = " + total);