访问者(Visitor)模式:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。访问者模式的结构图如下:
具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。
抽象元素(Visitable)角色:声明一个接受操作,接受一个访问者对象作为一个参数。
具体元素结点(ConcreteElement)角色:实现抽象结点所规定的接受操作。
数据结构对象(ObjectStructure)角色:可以遍历结构中的所有元素,提供一个接口让访问者对象都可以访问每一个元素。
package visitor; /** * *作者:alaric *时间:2013-9-13下午11:31:28 *描述:抽象访问者 */ public interface Visitor { public void visit(ConcreteElementB able ); public void visit(ConcreteElementA able ); }
package visitor; /** * *作者:alaric *时间:2013-9-13下午11:31:46 *描述:抽象角色元素 */ public interface Visitable { public void accept(Visitor v); }
package visitor; /** * *作者:alaric *时间:2013-9-13下午11:33:29 *描述:具体访问者A */ public class ConcreteVisitorA implements Visitor{ @Override public void visit(ConcreteElementB able) { able.operate(); } @Override public void visit(ConcreteElementA able) { // TODO Auto-generated method stub able.operate(); } }
package visitor; /** * *作者:alaric *时间:2013-9-13下午11:32:55 *描述:具体访问者B */ public class ConcreteVisitorB implements Visitor{ @Override public void visit(ConcreteElementB able) { able.operate(); } @Override public void visit(ConcreteElementA able) { // TODO Auto-generated method stub able.operate(); } }
package visitor; /** * *作者:alaric *时间:2013-9-13下午11:34:02 *描述:具体元素A */ public class ConcreteElementA implements Visitable { @Override public void accept(Visitor v) { v.visit(this); } public void operate(){ System.out.println("ConcreteElementA ...."); } }
package visitor; /** * *作者:alaric *时间:2013-9-13下午11:33:40 *描述:具体元素B */ public class ConcreteElementB implements Visitable { @Override public void accept(Visitor v) { v.visit(this); } public void operate(){ System.out.println("ConcreteElementB ...."); } }
package visitor; import java.util.ArrayList; import java.util.List; /** * *作者:alaric *时间:2013-9-13下午11:34:22 *描述:客户端 */ public class Client { /** * @param args */ public static void main(String[] args) { Visitor v1 = new ConcreteVisitorA(); List<Visitable> list = new ArrayList<>(); list.add(new ConcreteElementA()); list.add(new ConcreteElementB()); for(Visitable able :list){ able.accept(v1); } } }
看了很多设计模式的书,讲访问者设计模式都要提到一个概念“双重分派”,所谓“分派”简单理解就是根据类的特性,特征进行选择,这些选择都是程序语言设计的特征,比如多态(重载,重写)等等,我个人不太注重概念,只要深入掌握面向对象的基础就很好理解了。
设计模式相对其他模式来说结构有点复杂,上面是访问者模式的模拟实现,为了利于学习找了个真实的例子。dom4j里面利用访问者模式来对xml文档进行逐个节点访问,所有文档的对象的父类接口都是Node,对于不同类型的文档对象又做了不同的抽象,所有可能访问的节点如Visitor类中所示,dom4j中定义的Visitor接口如下:
/* * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. * * This software is open source. * See the bottom of this file for the licence. */ package org.dom4j; /** * <p> * <code>Visitor</code> is used to implement the <code>Visitor</code> * pattern in DOM4J. An object of this interface can be passed to a * <code>Node</code> which will then call its typesafe methods. Please refer * to the <i>Gang of Four </i> book of Design Patterns for more details on the * <code>Visitor</code> pattern. * </p> * * <p> * This <a href="http://www.patterndepot.com/put/8/JavaPatterns.htm">site </a> * has further discussion on design patterns and links to the GOF book. This <a * href="http://www.patterndepot.com/put/8/visitor.pdf">link </a> describes the * Visitor pattern in detail. * </p> * * @author <a href="mailto:[email protected]">James Strachan </a> * @version $Revision: 1.8 $ */ public interface Visitor { /** * <p> * Visits the given <code>Document</code> * </p> * * @param document * is the <code>Document</code> node to visit. */ void visit(Document document); /** * <p> * Visits the given <code>DocumentType</code> * </p> * * @param documentType * is the <code>DocumentType</code> node to visit. */ void visit(DocumentType documentType); /** * <p> * Visits the given <code>Element</code> * </p> * * @param node * is the <code>Element</code> node to visit. */ void visit(Element node); /** * <p> * Visits the given <code>Attribute</code> * </p> * * @param node * is the <code>Attribute</code> node to visit. */ void visit(Attribute node); /** * <p> * Visits the given <code>CDATA</code> * </p> * * @param node * is the <code>CDATA</code> node to visit. */ void visit(CDATA node); /** * <p> * Visits the given <code>Comment</code> * </p> * * @param node * is the <code>Comment</code> node to visit. */ void visit(Comment node); /** * <p> * Visits the given <code>Entity</code> * </p> * * @param node * is the <code>Entity</code> node to visit. */ void visit(Entity node); /** * <p> * Visits the given <code>Namespace</code> * </p> * * @param namespace * is the <code>Namespace</code> node to visit. */ void visit(Namespace namespace); /** * <p> * Visits the given <code>ProcessingInstruction</code> * </p> * * @param node * is the <code>ProcessingInstruction</code> node to visit. */ void visit(ProcessingInstruction node); /** * <p> * Visits the given <code>Text</code> * </p> * * @param node * is the <code>Text</code> node to visit. */ void visit(Text node); } /* * Redistribution and use of this software and associated documentation * ("Software"), with or without modification, are permitted provided that the * following conditions are met: * * 1. Redistributions of source code must retain copyright statements and * notices. Redistributions must also contain a copy of this document. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The name "DOM4J" must not be used to endorse or promote products derived * from this Software without prior written permission of MetaStuff, Ltd. For * written permission, please contact [email protected]. * * 4. Products derived from this Software may not be called "DOM4J" nor may * "DOM4J" appear in their names without prior written permission of MetaStuff, * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd. * * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org * * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. */dom4j里面有个缺省的访问者(Visitor)的实现VisitorSupport,我们解析一个文档只需继承这个类,然后重写visit方法即可。一个简单的类图表示dom4j是怎么利用visitor设计模式的,如下图:
<?xml version="1.0" encoding="UTF-8"?> <table name="test"> <rows> <row> <id>1</id> <test>Test</test> </row> <row> <id>2</id> <test>Test2</test> </row> </rows </table>我们写个客户端测试,为了简单,把Visitor作为内部类,直接就一个类完成,代码如下:
package com.alaric.dom4j; import java.io.File; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.VisitorSupport; import org.dom4j.io.SAXReader; public class Dom4jTest { public class MyVisitor extends VisitorSupport { public void visit(Attribute node){ System.out.println("属性 : "+node.getName()+" = "+node.getValue()); } public void visit(Element node){ if(node.isTextOnly()){ System.out.println("节点: "+node.getName()+" = "+node.getText()); }else{ System.out.println("节点:"+node.getName()); } } } public static void main(String[] args) throws Exception { SAXReader saxReader=new SAXReader(); File file=new File("d:\\test.xml"); try{ Document doc=saxReader.read(file); doc.accept(new Dom4jTest(). new MyVisitor()); }catch(DocumentException de){ de.printStackTrace(); } } }
运行结果:
节点:table
属性 : name = test
节点:rows
节点:row
节点: id = 1
节点: test = Test
节点:row
节点: id = 2
节点: test = Test2
可以看出把xml节点顺序的访问了一边。每个人可以根据不同的xml来实现自己的Visitor,不论怎么写都可以遍历出你所有的节点,这就是visitor的厉害之处。访问者模式也不是万能的,他的缺点是当数据结构变化时,他的visitor接口及其实现都要改变。所以访问者模式不能使用在经常变化的数据接口上。在Gof的设计模式中,有以下情形可以考虑使用设计模式:
1、一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
3、当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4、 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
这些个人看来都是建议,项目中还要具体问题具体分析了。