集合类型在面向对象编程中很常用,这也带来一些代码相关的问题。比如,“怎么操作集合中不同类型的对象?”
一种做法就是遍历集合中的每个元素,然后根据它的类型而做具体的操作。这会很复杂,尤其当你不知道集合中元素的类型时。如果y要打印集合中的元素,可以写一个这样的方法:
- publicvoid messyPrintCollection(Collection collection) {
- Iterator iterator = collection.iterator()
- while(iterator.hasNext())
- System.out.println(iterator.next().toString())
- }
- publicvoid messyPrintCollection(Collection collection) {
- Iterator iterator = collection.iterator()
- while(iterator.hasNext()) {
- Object o = iterator.next();
- if(o instanceofCollection)
- messyPrintCollection((Collection)o);
- else
- System.out.println(o.toString());
- }
- }
- publicvoid messyPrintCollection(Collection collection) {
- Iterator iterator = collection.iterator()
- while(iterator.hasNext()) {
- Object o = iterator.next();
- if(o instanceofCollection)
- messyPrintCollection((Collection)o);
- elseif (o instanceofString)
- System.out.println("'"+o.toString()+"'");
- elseif (o instanceofFloat)
- System.out.println(o.toString()+"f");
- else
- System.out.println(o.toString());
- }
- }
为实现访问者模式,你需要创建一个Visitor接口,为被访问的集合对象创建一个Visitable接口。接下来需要创建具体的类来实现Visitor和Visitable接口。这两个接口大致如下:
- publicinterface Visitor {
- publicvoid visitCollection(Collection collection);
- publicvoid visitString(String string);
- publicvoid visitFloat(Float float);
- }
- publicinterface Visitable {
- publicvoid accept(Visitor visitor);
- }
- publicclass VisitableString implementsVisitable {
- privateString value;
- publicVisitableString(String string) {
- value = string;
- }
- publicvoid accept(Visitor visitor) {
- visitor.visitString(this);
- }
- }
- visitor.visitString(this)
具体Visitor的实现方式如下:
- publicclass PrintVisitor implementsVisitor {
- publicvoid visitCollection(Collection collection) {
- Iterator iterator = collection.iterator();
- while(iterator.hasNext()) {
- Object o = iterator.next();
- if(o instanceofVisitable)
- ((Visitable)o).accept(this);
- }
- publicvoid visitString(String string) {
- System.out.println("'"+string+"'");
- }
- publicvoid visitFloat(Float float) {
- System.out.println(float.toString()+"f");
- }
- }
尽管实现visitor后,if-else语句不见了,但还是引入了很多附加的代码。你不得不将原始的对象——String和Float,打包到一个实现Visitable接口的类中。虽然很烦人,但这一般来说不是个问题。因为你可以限制被访问集合只能包含Visitable对象。
然而,这还有很多附加的工作要做。更坏的是,当你想增加一个新的Visitable类型时怎么办,比如VisitableInteger?这是访问者模式的一个主要缺点。如果你想增加一个新的Visitable类型,你不得不改变Visitor接口以及每个实现Visitor接口方法的类。你可以不把Visitor设计为接口,取而代之,可以把Visitor设计为一个带有空操作的抽象基类。这与Java GUI中的Adapter类很相似。这么做的问题是你会用尽单次继承,而常见的情形是你还想用继承实现其他功能,比如继承StringWriter类。这同样只能成功访问实现Visitable接口的对象。
幸运的是,Java可以让你的访问者模式更灵活,你可以按你的意愿增加Visitable对象。怎么实现呢?答案是使用反射。使用反射的ReflectiveVisitor接口只需要一个方法:
- publicinterface ReflectiveVisitor {
- publicvoid visit(Object o);
- }
- publicclass PrintVisitor implementsReflectiveVisitor {
- publicvoid visitCollection(Collection collection)
- { ... same as above ... }
- publicvoid visitString(String string)
- { ... same as above ... }
- publicvoid visitFloat(Float float)
- { ... same as above ... }
- publicvoid default(Object o)
- {
- System.out.println(o.toString());
- }
- publicvoid 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,
- newClass[] { o.getClass() });
- // Try to invoke visitFoo(Foo foo)
- m.invoke(this,newObject[] { o });
- }catch(NoSuchMethodException e) {
- // No method, so do the default implementation
- default(o);
- }
- }
- }
在新的PrintVisitor类中,有对应于Collections、String和Float的操作方法;对于不能处理的类型,可以通过catch语句捕捉。对于不能处理的类型,可以通过扩展visit()方法来尝试处理它们的所有超类。首先,增加一个新的方法getMethod(Class c),返回值是一个可被触发的方法。它会搜索Class c的所有父类和接口,以找到一个匹配方法。
- protectedMethod 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('.') + 1);
- try{
- m = getClass().getMethod(method, newClass[] {newc});
- }catch(NoSuchMethodException e) {
- newc = newc.getSuperclass();
- }
- }
- // Try the interfaces. If necessary, you
- // can sort them first to define 'visitable' interface wins
- // in case an object implements more than one.
- if(newc == Object.class) {
- Class[] interfaces = c.getInterfaces();
- for(inti = 0; i < interfaces.length; i++) {
- String method = interfaces[i].getName();
- method = "visit"+ method.substring(method.lastIndexOf('.') + 1);
- try{
- m = getClass().getMethod(method, newClass[] {interfaces[i]});
- }catch(NoSuchMethodException e) {}
- }
- }
- if(m == null) {
- try{
- m = thisclass.getMethod("visitObject",newClass[] {Object.class});
- }catch(Exception e) {
- // Can't happen
- }
- }
- returnm;
- }
由于大家对传统的访问者模式比较熟悉,这里沿用了之前方法命名的惯例。但是,有些人可能注意到,把所有的方法都命名为“visit”并通过参数类型不同来区分,这样更高效。然而,如果你这么做,你必须把visit(Object o)方法的名称改为其他,比如dispatch(Object o)。否则,(当没有对应处理方法时),你无法退回到默认的处理方法,并且当你调用visit(Object o)方法时,为了确保正确的方法调用,你必须将参数强制转化为Object。
为了利用getMethod()方法,现在需要修改一下visit()方法。
- publicvoid visit(Object object) {
- try{
- Method method = getMethod(getClass(), object.getClass());
- method.invoke(this,newObject[] {object});
- }catch(Exception e) { }
- }
我仍保留Visitable接口是有原因的。传统访问者模式的另一个好处是它可以通过Visitable对象控制对象结构的遍历顺序。举例来说,假如有一个实现了Visitable接口的类TreeNode,它在accept()方法中遍历自己的左右节点。
- publicvoid accept(Visitor visitor) {
- visitor.visitTreeNode(this);
- visitor.visitTreeNode(leftsubtree);
- visitor.visitTreeNode(rightsubtree);
- }
- publicvoid visit(Object object) throwsException
- {
- Method method = getMethod(getClass(), object.getClass());
- method.invoke(this,newObject[] {object});
- if(object instanceofVisitable)
- {
- callAccept((Visitable) object);
- }
- }
- publicvoid callAccept(Visitable visitable) {
- visitable.accept(this);
- }
当使用几个不同的visitor去操作同一个对象集合时,访问者模式的力量就会展现出来。比如,当前有一个解释器、中序遍历器、后续遍历器、XML编写器以及SQL编写器,它们可以处理同一个对象集合。我可以轻松地为这个集合再写一个先序遍历器或者一个SOAP编写器。另外,它们可以很好地兼容它们不识别的类型,或者我愿意的话可以让它们抛出异常。
总结
使用Java反射,可以使访问者模式提供一种更加强大的方式操作对象结构,可以按照需求灵活地增加新的Visitable
类型。我希望在你的编程之旅中可以使用访问者模式。
Jeremy Blosser有5年的Java编程经验,他在很多软件公司工作过。他现在在一家创业型公司Software Instruments供职。你可以访问Jeremy的网站http://www.blosser.org
了解更多
- Patterns homepage
- [Design PatternsElements of Reusable Object-Oriented Software, Erich Gamma, et al. (Addison-Wesley, 1995)](http://www.amazon.com/exec/obidos/ASIN/0201633612/o/qid=963253562/sr=2-1/002-9334573-2800059)
- Patterns in Java, Volume 1, Mark Grand (John Wiley & Sons, 1998)
- Patterns in Java, Volume 2, Mark Grand (John Wiley & Sons, 1999)
- View all previous Java Tips and submit your own
原文链接: javaworld 翻译: ImportNew.com - 文 学敏
译文链接: http://www.importnew.com/12536.html
[ 转载请保留原文出处、译者和译文链接。 ]