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);
}
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