Visitor模式理解

来源:互联网 发布:人工智能 产品 编辑:程序博客网 时间:2024/05/16 07:16

访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。

简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。


Visitor模式的组成结构:

  1) 访问者角色(Visitor):声明一个访问接口。接口的名称和方法的参数标识了向访问者发送请求的元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。

  2) 具体访问者角色(Concrete Visitor):实现访问者角色(Visitor)接口

  3) 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。

  4) 具体元素角色(Concrete Element):实现元素角色(Element)接口。

  5) 对象结构角色(Object Structure):这是使用Visitor模式必须的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口允许访问者角色访问它的元素;可以是一个组合(组合模式)或是一个集合,如一个列表或一个无序集合。

类图如下:



示例:

假设一个火星人和一个地球人都要对美国和俄罗斯进行国事访问(其实是去玩),那么他们就是包含visit方法的访问者;美国和俄罗斯碍于情面也只得接待他们,于是两个国家就是包含accept方法的具体元素。


对火星人与地球人进行抽象,他们都是访问者,于是定义Visitor接口:

public interface Visitor {    /**     * 对美国进行访问     * @param america     */    public void visit(America america);    /**     * 对俄罗斯进行访问     * @param russia     */    public void visit(Russia russia);}

火星人实现类:

/** * 火星人 */public class Martian implements Visitor {    @Override    public void visit(America america) {        System.out.println(this.toString() + ", 我去看过" + america.getViewSpots());    }    @Override    public void visit(Russia russia) {        System.out.println(this.toString() + ", 我太忙了,先回去了,下次再来看!");    }    @Override    public String toString() {        return "火星人-访问者";    }}

地球人实现类:

/** * 地球人 */public class Earthman implements Visitor {    @Override    public void visit(America america) {        System.out.println(this.toString() + ", 我去看过" + america.getViewSpots());    }    @Override    public void visit(Russia russia) {        System.out.println(this.toString() + ", 我去看过" + russia.getViewSpots());    }    @Override    public String toString() {        return "地球人-访问者";    }}

对美国和俄罗斯进行抽象,他们都是有很多旅游景点的国家,都要对来参观的人进行接待,于是定义Country接口:
public interface Country {    /**     * 对访问者进行接待     * @param visitor     */    public void accept(Visitor visitor);    /**     * 列举旅游景点     */    public String getViewSpots();}



美国实现类:
public class America implements Country {    public void accept(Visitor visitor) {        System.out.println("欢迎"+visitor + "光临美国!");        visitor.visit(this);    }    @Override    public String getViewSpots() {        return "白宫,好莱坞,阿拉斯加";    }}

俄罗斯实现类:
public class Russia implements Country {    @Override    public void accept(Visitor visitor) {        System.out.println("欢迎"+visitor + "光临俄罗斯!");        visitor.visit(this);    }    @Override    public String getViewSpots() {        return "克里姆林宫, 贝加尔湖, 红场";    }}

测试类:
public class Test {    public static void main(String[] args) {        //火星人与地球人开始对美国和俄罗斯进行访问        Visitor martian = new Martian();        Visitor earthman = new Earthman();        Country america = new America();        Country russia = new Russia();        // 美国进行接待        america.accept(martian);        america.accept(earthman);        // 俄罗斯进行接待        russia.accept(martian);        russia.accept(earthman);    }}

运行结果:

欢迎火星人-访问者光临美国!火星人-访问者, 我去看过白宫,好莱坞,阿拉斯加欢迎地球人-访问者光临美国!地球人-访问者, 我去看过白宫,好莱坞,阿拉斯加欢迎火星人-访问者光临俄罗斯!火星人-访问者, 我太忙了,先回去了,下次再来看!欢迎地球人-访问者光临俄罗斯!地球人-访问者, 我去看过克里姆林宫, 贝加尔湖, 红场


更多访问者模式理解可以参考: http://www.cnblogs.com/zhenyulu/articles/79719.html 


访问者模式的使用实例 -- dom4j


dom4j使用visitor模式,我想是因为dom元素的数据结构相对稳定(只能包含Document、Element、Attribute、Namespace、Text等元素标签),而解析dom元素的算法又取决于具体的应用。

下面列出其部分源代码:

Visitor接口:

/** <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://rampages.onramp.net/~huston/dp/patterns.html">article</a>  * has further discussion on design patterns and links to the GOF book.  * This <a href="http://rampages.onramp.net/~huston/dp/visitor.html">link</a>  * describes the Visitor pattern in detail.  * </p>  *  * @author <a href="mailto:james.strachan@metastuff.com">James Strachan</a>  * @version $Revision: 1.2 $  */public interface Visitor {    /** <p>Visits the given <code>Document</code></p>      *      * @param node is the <code>Document</code> node to visit.      */    public void visit(Document document);    /** <p>Visits the given <code>DocumentType</code></p>      *      * @param node is the <code>DocumentType</code> node to visit.      */    public void visit(DocumentType documentType);    /** <p>Visits the given <code>Element</code></p>      *      * @param node is the <code>Element</code> node to visit.      */    public void visit(Element node);    /** <p>Visits the given <code>Attribute</code></p>      *      * @param node is the <code>Attribute</code> node to visit.      */    public void visit(Attribute node);}

/** * <p> * <code>VisitorSupport</code> is an abstract base class which is useful for * implementation inheritence or when using anonymous inner classes to create * simple <code>Visitor</code> implementations. * </p> *  * @author <a href="mailto:james.strachan@metastuff.com">James Strachan </a> * @version $Revision: 1.6 $ */public abstract class VisitorSupport implements Visitor {    public VisitorSupport() {    }    public void visit(Document document) {    }    public void visit(DocumentType documentType) {    }    public void visit(Element node) {    }    public void visit(Attribute node) {    }    public void visit(CDATA node) {    }    public void visit(Comment node) {    }    public void visit(Entity node) {    }    public void visit(Namespace namespace) {    }    public void visit(ProcessingInstruction node) {    }    public void visit(Text node) {    }}

/** * <p> * <code>Node</code> defines the polymorphic behavior for all XML nodes in a * dom4j tree. * </p> *  * <p> * A node can be output as its XML format, can be detached from its position in * a document and can have XPath expressions evaluated on itself. * </p> *  * <p> * A node may optionally support the parent relationship and may be read only. * </p> *  * @author <a href="mailto:jstrachan@apache.org">James Strachan </a> * @version $Revision: 1.31 $ *  * @see #supportsParent * @see #isReadOnly */public interface Node extends Cloneable {/**     * <p>     * <code>accept</code> is the method used in the Visitor Pattern.     * </p>     *      * @param visitor     *            is the visitor in the Visitor Pattern     */    void accept(Visitor visitor);    /**     * <p>     * <code>clone</code> will return a deep clone or if this node is     * read-only then clone will return the same instance.     * </p>     *      * @return a deep clone of myself or myself if I am read only.     */    Object clone();}

/** <p><code>Branch</code> interface defines the common behaviour   * for Nodes which can contain child nodes (content) such as   * XML elements and documents.   * This interface allows both elements and documents to be treated in a   * polymorphic manner when changing or navigating child nodes (content).</p>  *  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>  * @version $Revision: 1.25 $  */public interface Branch extends Node {。。。}


/** <p><code>Document</code> defines an XML Document.</p>  *  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>  * @version $Revision: 1.8 $  */public interface Document extends Branch {    /** Returns the root {@link Element} for this document.      *      * @return the root element for this document      */    public Element getRootElement();        /** Sets the root element for this document      *      * @param rootElement the new root element for this document      */        public void setRootElement(Element rootElement);。。。}


/** * <p> * <code>AbstractDocument</code> is an abstract base class for tree * implementors to use for implementation inheritence. * </p> *  * @author <a href="mailto:jstrachan@apache.org">James Strachan </a> * @version $Revision: 1.33 $ */public abstract class AbstractDocument extends AbstractBranch implements        Document {/**     * <p>     * <code>accept</code> method is the <code>Visitor Pattern</code>     * method.     * </p>     *      * @param visitor     *            <code>Visitor</code> is the visitor.     */    public void accept(Visitor visitor) {        visitor.visit(this);        DocumentType docType = getDocType();        if (docType != null) {            visitor.visit(docType);        }        // visit content        List content = content();        if (content != null) {            for (Iterator iter = content.iterator(); iter.hasNext();) {                Object object = iter.next();                if (object instanceof String) {                    Text text = getDocumentFactory()                            .createText((String) object);                    visitor.visit(text);                } else {                    Node node = (Node) object;                    node.accept(visitor);                }            }        }    }}

/** <p><code>Element</code> interface defines an XML element.  * An element can have declared namespaces, attributes, child nodes and   * textual content.</p>  *  * <p>Some of this interface is optional.   * Some implementations may be read-only and not support being modified.  * Some implementations may not support the parent relationship and methods  * such as {@link #getParent} or {@link #getDocument}.</p>  *  *  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>  * @version $Revision: 1.36 $  */public interface Element extends Branch {。。。。}


public abstract class AbstractElement extends AbstractBranch implements        org.dom4j.Element {/**     * <p>     * <code>accept</code> method is the <code>Visitor Pattern</code>     * method.     * </p>     *      * @param visitor     *            <code>Visitor</code> is the visitor.     */    public void accept(Visitor visitor) {        visitor.visit(this);        // visit attributes        for (int i = 0, size = attributeCount(); i < size; i++) {            Attribute attribute = attribute(i);            visitor.visit(attribute);        }        // visit content        for (int i = 0, size = nodeCount(); i < size; i++) {            Node node = node(i);            node.accept(visitor);        }    }}


下面是使用dom4j发起webservice调用,并解析结果到map的实现:

package pattern.visitor;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.dom4j.*;import org.dom4j.io.SAXReader;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.io.OutputStream;import java.lang.reflect.Field;import java.math.BigDecimal;import java.net.HttpURLConnection;import java.net.URL;import java.util.*;import java.util.regex.Matcher;import java.util.regex.Pattern;/** * 模拟soap调用 */public abstract class Webservice extends VisitorSupport {// ------------------------------ FIELDS ------------------------------    protected Log logger = LogFactory.getLog(getClass());    private String responseMessageXML = "";    private Map<String, String> responseMessageMap = new HashMap<String, String>();    private Map<String, Object> responseObjectMap = new HashMap<String, Object>();    private Document document = null;// ------------------------ INTERFACE METHODS ------------------------// --------------------- Interface Visitor ---------------------    @Override    public void visit(Element node) {        Iterator iterator = node.elementIterator();        if (iterator.hasNext()) {            //get fail reason            Element reason = node.element("Reason");            if (reason != null) {                Iterator reasonIter = reason.elementIterator();                if (reasonIter.hasNext()) {                    Element reasonText = (Element) reasonIter.next();                    String reasonStr = reasonText.getText();                    responseMessageMap.put("Reason", reasonStr);                }            }            if (responseMessageMap.get(node.getName()) != null) {                int count = Integer.parseInt(responseMessageMap.get(node.getName())) + 1;                responseMessageMap.put(node.getName(), count + "");            } else {                responseMessageMap.put(node.getName(), "1");            }            return;        }        if (responseMessageMap.get(node.getName()) != null) {            for (int i = 1; i < responseMessageMap.size(); i++) {                if (responseMessageMap.get(node.getName() + "" + i) == null) {                    responseMessageMap.put(node.getName() + "" + i, node.getText());                    break;                }            }        } else {            responseMessageMap.put(node.getName(), node.getText());        }        super.visit(node);    //To change body of overridden methods use File | Settings | File Templates.    }// --------------------- GETTER / SETTER METHODS ---------------------    public Map<String, String> getResponseMessageMap() {        return responseMessageMap;    }    public Map<String, Object> getResponseObjectMap() {        return responseObjectMap;    }    public String getErrorMsg() {        Object errMsg = "";        if (responseMessageMap != null) {            errMsg = responseMessageMap.get("Reason");            if (errMsg == null || "".endsWith(errMsg.toString())) {                errMsg = responseMessageMap.get("faultstring");            }        }        if (errMsg != null) {            return errMsg.toString();        }        return null;    }    public String getPropertyPrefix(String property) {        String prefix = "";        if (addActionPrefixToPropertys() && getAction() != null && getAction().contains(":")) {            prefix = getAction().split(":")[0] + ":";        }        return "<" + prefix + property + ">";    }    public String getPropertySuffix(String property) {        String prefix = "";        if (addActionPrefixToPropertys() && getAction() != null && getAction().contains(":")) {            prefix = getAction().split(":")[0] + ":";        }        return "</" + prefix + property + ">";    }    public Webservice request() throws Exception {        String wsdl = getWsdlUrl();        if (wsdl == null || "".equals(wsdl)) {            throw new Exception("wsdl url is null");        }        logger.warn(wsdl);        HttpURLConnection connection = null;        InputStream is = null;        OutputStream os = null;        String requestString = null;        try {            connection = (HttpURLConnection) new URL(wsdl).openConnection();            connection.setDoOutput(true);            connection.setDoInput(true);            connection.setRequestProperty("Method", "POST");            connection.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");            requestString = this.toString();            connection.setRequestProperty("Content-Length", requestString.length() + "");            connection.setRequestProperty("SOAPAction", getSoapAction());            connection.connect();            os = connection.getOutputStream();            os.write(requestString.getBytes());            logger.warn("=======================request=====================");            logger.warn(requestString);            os.flush();            //don't delete this line            String responseMessage = connection.getResponseMessage();            logger.warn(responseMessage);            InputStream errorStream = connection.getErrorStream();            if (errorStream != null) {                int i = 0;                byte[] ss = new byte[1024];                ByteArrayOutputStream bos = new ByteArrayOutputStream();                while ((i = errorStream.read(ss)) != -1) {                    bos.write(ss, 0, i);                }                bos.close();                String response = new String(bos.toByteArray());                responseMessageXML = response;                saveLog("WebService.request", requestString, responseMessageXML, null);                Document doc = DocumentHelper.parseText(response);                doc.accept(this);                errorStream.close();                return this;            }            is = connection.getInputStream();            int i = 0;            byte[] ss = new byte[1024];            ByteArrayOutputStream bos = new ByteArrayOutputStream();            while ((i = is.read(ss)) != -1) {                bos.write(ss, 0, i);            }            bos.close();          /*  byte[] bts = new byte[is.available()];            is.read(bts);*/            String response = new String(bos.toByteArray());            Document doc = DocumentHelper.parseText(response);            logger.warn("=======================response=====================");            logger.warn(response);            responseMessageXML = response;            saveLog("WebService.request", requestString, responseMessageXML, null);            doc.accept(this);        } catch (Exception ex) {            saveLog("WebService.request", requestString, null, ex.getMessage());            throw new Exception(ex);        } finally {            if (null != os) {                os.close();            }            if (null != is) {                is.close();            }            if (null != connection) {                connection.disconnect();            }        }        return this;    }    private void saveLog(String methodName, String inParm, Object outParm, String exception) {//        service.log(methodName, new Object[]{inParm}, outParm, exception);    }    @Override    public String toString() {        StringBuffer toString = new StringBuffer();        toString.append(getEnvelope() +                "   <soapenv:Header>\n" +                getAuth() +                "</soapenv:Header>\n" +                "   <soapenv:Body>\n" +                "      <" + getAction() + " " + getActionXmlns() + ">" +                getBody() +                "      </" + getAction() + ">\n" +                "   </soapenv:Body>\n" +                "</soapenv:Envelope>");        return toString.toString();    }    public abstract String getEnvelope();    public abstract String getAction();    public String getActionXmlns() {        return "";    }    public String getSoapAction() {        return "";    }    public String getAuth() {        return "";    }    public String getBodyRoot() {        return "";    }    public boolean addActionPrefixToPropertys() {        return true;    }    public abstract String getWsdlUrl();    public String getBody() {        Class cls = null;        StringBuffer toString = new StringBuffer();        if (!"".equals(getBodyRoot())) {            toString.append("<" + getBodyRoot() + ">");        }        try {            cls = Class.forName(getClass().getName());            Field[] fields = cls.getDeclaredFields();            for (Field field : fields) {                if (!"serialVersionUID".equals(field.getName()) && !"actionType".equals(field.getName())) {                    rendorElement(this, field, toString);                }                // field.setAccessible(false);            }        } catch (ClassNotFoundException e) {            e.printStackTrace(); // To change body of catch statement use File |            // Settings | File Templates.        } catch (IllegalAccessException e) {            e.printStackTrace(); // To change body of catch statement use File |            // Settings | File Templates.        }        if (!"".equals(getBodyRoot())) {            toString.append("</" + getBodyRoot() + ">");        }        return toString.toString(); // To change body of overridden methods use    }    private void rendorElement(Object obj, Field field, StringBuffer toString)            throws IllegalArgumentException, IllegalAccessException {        if (!"serialVersionUID".equals(field.getName()) && !"actionType".equals(field.getName())) {            if (obj == null || field == null) {                return;            }            field.setAccessible(true);            //toString.append("\t" + getPropertyPrefix(field.getName()));            Class type = field.getType();            if (type != List.class && !type.isArray()) {                toString.append("\t" + getPropertyPrefix(field.getName()));            }            if (isBaseType(type)) {                toString.append(getString(field.get(obj)));            } else if (type == List.class || type.isArray()) {                Iterable its = (Iterable) field.get(obj);                if (null == its) {                    return;                }                for (Object o : its) {                    Class cls = (Class) o.getClass();                    Field[] fields = cls.getDeclaredFields();                    toString.append("\t" + getPropertyPrefix(field.getName()));                    for (Field f : fields) {                        rendorElement(o, f, toString);                    }                    toString.append(getPropertySuffix(field.getName()));                }            } else {                Class cls = (Class) type;                Field[] fields = cls.getDeclaredFields();                for (Field f : fields) {                    rendorElement(field.get(obj), f, toString);                }            }            if (type != List.class && !type.isArray()) {                toString.append(getPropertySuffix(field.getName())).append("\n");            }        }    }    public String getResponseMessageXML() {//getResponseMessageXML        return responseMessageXML;    }    private boolean isBaseType(Class type) {        if (type == String.class || type.isPrimitive() || type == Double.class                || type == Float.class || type == Short.class                || type == Integer.class || type == Boolean.class                || type == Long.class || type == Character.class                || type == Byte.class || type == Date.class                || type == java.sql.Date.class                || type == java.sql.Timestamp.class || type == BigDecimal.class) {            return true;        }        return false;    }    private String getString(Object o) {        if (null == o || "null".equals(o)) {            return "";        }        return o.toString().trim();    }    public String getResponseByPath(String path) {        String header = "//soap:Envelope/soap:Body";        try {//            if (document == null) {            SAXReader reader = new SAXReader();            Map nameSpace = getNameSpace(responseMessageXML);            reader.getDocumentFactory().setXPathNamespaceURIs(nameSpace);            document = reader.read(new ByteArrayInputStream(getResponseMessageXML().getBytes("UTF-8")));//            }            if (!path.startsWith("/")) {                path = "/" + path;            }            Node node = document.selectSingleNode(header + path);            if (null != node) return node.getText();            return "";        } catch (Exception e) {            logger.error(e.getMessage(), e);            throw new RuntimeException("Could not find node, may be your path is malformed.");        }    }    public int getCount(String parent, String nodeName) {        String header = "//soap:Envelope/soap:Body";        try {//            if (document == null) {            SAXReader reader = new SAXReader();            Map nameSpace = getNameSpace(responseMessageXML);            reader.getDocumentFactory().setXPathNamespaceURIs(nameSpace);            document = reader.read(new ByteArrayInputStream(getResponseMessageXML().getBytes("UTF-8")));//            }            List<Node> nodes = document.selectNodes(header + parent);            if (nodes != null && nodes.size() > 0) {                Node node = nodes.get(0);                List list = node.selectNodes(header + parent + "/" + nodeName);                return list.size();            }            return 0;        } catch (Exception e) {            logger.error(e.getMessage(), e);            throw new RuntimeException("Could not find node, may be your path is malformed.");        }    }    private Map getNameSpace(String responseMessageXML) {        Pattern pattern2 = Pattern.compile("xmlns:(.*?)=\"(.*?)\"");        Matcher matcher2 = pattern2.matcher(responseMessageXML);        Map<String, String> nsMap = new HashMap<String, String>();        while (matcher2.find()) {            int i = 1;            String prefix = matcher2.group(i);            String url = matcher2.group(i + 1);            nsMap.put(prefix, url);            i = i + 2;        }        return nsMap;    }    public String getMethodName(String soap) {        String header = "/soapenv:Envelope/soapenv:Body/child::*";        String path = "";        try {//            if (document == null) {            SAXReader reader = new SAXReader();            Map nameSpace = getNameSpace(soap);            reader.getDocumentFactory().setXPathNamespaceURIs(nameSpace);            document = reader.read(new ByteArrayInputStream(soap.getBytes("UTF-8")));//            }            if (!path.startsWith("/")) {//                path = "/" + path;            }            Node node = document.selectSingleNode(header + path);            if (null != node) {                String nodeName = node.getName();                return nodeName;            }            return "";        } catch (Exception e) {            logger.error(e.getMessage(), e);            throw new RuntimeException("Could not find node, may be your path is malformed.");        }    }}

测试类:

import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import java.io.Serializable;import java.util.Map;public class HttpTest {    protected static Log logger = LogFactory.getLog(HttpTest.class);    public static Map<String, String> call(Webservice request) throws Exception{        Webservice handler = null;        try{            handler = request.request();            if(null != handler.getErrorMsg() && !"".equals(handler.getErrorMsg())){                logger.error(handler.getErrorMsg());                throw new Exception(handler.getErrorMsg());            }        }catch(Exception ex){            logger.error("Invoke vendor API error:"+ex);            throw ex;        }        return handler.getResponseMessageMap();    }    public static void main(String[] args) {        TestRequest request = new TestRequest();        request.setUserId("1");        try {            Map<String,String> map = call(request);            System.out.println(map.get("queryResult"));        } catch (Exception e) {            e.printStackTrace();        }    }}class TestRequest extends Webservice implements Serializable {    private static final long serialVersionUID = -348238267486316198L;    private String userId;    public String getUserId() {        return userId;    }    public void setUserId(String userId) {        this.userId = userId;    }    @Override    public String getEnvelope(){        return "<soapenv:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"" +                " xmlns:soapenv=\"http://www.w3.org/2003/05/soap-envelope\"> \n";    }    @Override    public String getAction() {        return "queryByUserId";    }    @Override    public String getWsdlUrl() {        return "http://localhost/test?wsdl";    }}







0 0
原创粉丝点击