一、定義
GOF中,對Visitor模式的意圖是這樣描述的:表示一個作用於某對象結構中的各元素的操作。它使得你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。
從描述來看,訪問者模式主要用於擴展現有的類層次結構來實現新的行爲。一般的擴展方法是添加新的方法以提供新的行爲。但是有時候,新行爲可能和現有對象根本就不兼容。還有可能,類層次的開發人員無法預知以後的開發過程中將會需要哪些功能。爲了方便後來的開發者可以在軟件開發過程中方便地擴展該類層次結構的行爲,開發人員可以在設計中使用訪問者模式。
二、應用場景
考慮一個編譯器,它將源程序表示爲一個抽象的語法樹。編譯器需要對樹節點實施某些操作以進行“靜態語意”分析。它可能要定義許多操作進行類型檢查、代碼優化、流程分析等等。這些操作大多要對不同的節點進行不同的處理。例如對代表賦值語句的節點的處理就不同於代表變量或算術表達式的節點的處理。注意,這裏有兩個維度的變化:編譯器需要定義各種不同的操作,每種操作針對不同節點流程也不同。將這些操作分散到各種節點類中會導致整個系統難以理解、難以維護。不同種的操作如類型檢查和流程分析代碼放在一起,將產生混亂。若可以實現獨立地增加新的操作,將相似的操作集中起來,並使得這些節點獨立於作用其上的操作,無疑能使得系統變得更容易擴展。
可以將每一個類中的相關操作包裝在一個獨立的對象(稱爲一個Visitor)中,並在遍歷抽象語法樹時將這個對象傳遞給當前訪問的元素。當一個元素“接受”該訪問者時,該元素向訪問者發送一個包含自身類信息的請求(將自己this作爲參數傳遞給訪問者)。然後訪問者將爲該元素執行相應操作。訪問者將使用節點類提供的公共接口來訪問節點對象的屬性。
使用Visitor模式,需要定義兩個類層次:一個對應於接受操作的元素(Element)層次,另一個對應於定義對元素操作的訪問者(Visitor)層次。給訪問者類層次增加一個新的子類即可創建一類新的操作。
三、類圖
四、代碼示例
package inode;
import visitor.*;
public interface INode {
int getFileNum();
void Add(INode node);
void Remove(INode node);
public void Accept(Visitor v);
}
INodeFile.java
package inode;
import visitor.Visitor;
public class INodeFile implements INode {
String name;
INodeFile(String name)
{
this.name=name;
}
@Override
public int getFileNum() {
return 1;
}
public String getFileName()
{
return this.name;
}
@Override
public void Add(INode node) {
System.out.println("File-----No subdirectory");
}
@Override
public void Remove(INode node) {
System.out.println("File-----No subdirectory");
}
@Override
public void Accept(Visitor v) { //double dispatch
v.visitINodeFile(this);
}
}
INodeDirectory.java
package inode;
import java.util.*;
import visitor.Visitor;
public class INodeDirectory implements INode {
private LinkedList<INode> list;
String name;
INodeDirectory(String name)
{
this.name=name;
list=new LinkedList<INode>();
}
@Override
public int getFileNum() {
int sum=0;
for(INode temp:list)
{
sum+=temp.getFileNum();
}
return sum;
}
public String getDirName()
{
return this.name;
}
@Override
public void Add(INode node) {
list.add(node);
}
@Override
public void Remove(INode node) {
list.remove(node);
}
@Override
public void Accept(Visitor v) {
v.visitINodeDirectory(this); //double dispatch
for(int i=0;i<list.size();i++)
{
list.get(i).Accept(v);
}
}
}
Visitor類層次結構
package visitor;
import inode.*;
public interface Visitor {
public void visitINodeFile(INodeFile file);
public void visitINodeDirectory(INodeDirectory dir);
}
package visitor;
import inode.INodeDirectory;
import inode.INodeFile;
public class NumberVisitor implements Visitor {
private int filenumber;
public NumberVisitor()
{
this.filenumber=0;
}
public int getAllNumber()
{
return this.filenumber;
}
@Override
public void visitINodeFile(INodeFile file) {
this.filenumber++;
}
@Override
public void visitINodeDirectory(INodeDirectory dir) {
this.filenumber++;
}
}
package visitor;
import inode.INodeDirectory;
import inode.INodeFile;
public class NameVisitor implements Visitor {
private StringBuilder name=null;
public NameVisitor()
{
this.name=new StringBuilder();
}
public String getAllname()
{
return name.toString();
}
@Override
public void visitINodeFile(INodeFile file) {
name.append("File: "+file.getFileName()+"|");
}
@Override
public void visitINodeDirectory(INodeDirectory dir) {
name.append("Directory: "+dir.getDirName()+"|");
}
}
package inode;
import visitor.*;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
INode plant=new INodeDirectory("plant");
INode root=new INodeDirectory("root");
INode music=new INodeFile("music");
INode pdf=new INodeFile("pdf");
INode movie=new INodeFile("movie");
plant.Add(music);
plant.Add(pdf);
plant.Add(movie);
INode book=new INodeFile("book");
root.Add(plant);
root.Add(book);
NumberVisitor numvis=new NumberVisitor();
root.Accept(numvis);
System.out.println("Total number: "+numvis.getAllNumber());
NameVisitor namevis=new NameVisitor();
root.Accept(namevis);
System.out.println("All Files: "+namevis.getAllname());
}
}