訪問者(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、 定義對象結構的類很少改變,但經常需要在此結構上定義新的操作。改變對象結構類需要重定義對所有訪問者的接口,這可能需要很大的代價。如果對象結構類經常改變,那麼可能還是在這些類中定義這些操作較好。
這些個人看來都是建議,項目中還要具體問題具體分析了。
設計模式系列目錄: