`
java-mans
  • 浏览: 11415409 次
文章分类
社区版块
存档分类
最新评论

设计模式笔记24:访问者模式(Visitor Pattern)

 
阅读更多

一、访问者模式的内容

访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。

  1. 对于系统中的某些对象,它们存储在同一个集合中,且具有不同的类型,而且对于该集合中的对象,可以接受一类称为访问者的对象来访问,而且不同的访问者其访问方式有所不同,访问者模式为解决这类问题而诞生。
  2. 在实际使用时,对同一集合对象的操作并不是唯一的,对相同的元素对象可能存在多种不同的操作方式。
  3. 而且这些操作方式并不稳定,可能还需要增加新的操作,以满足新的业务需求。
  4. 此时,访问者模式就是一个值得考虑的解决方案。
  5. 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式,这就是访问者模式的模式动机。
问题提出
System.Collection命名空间下提供了大量集合操作对象。但大多数情况下处理的都是同类对象的聚集。换言之,在聚集上采取的操作都是一些针对同类型对象的同类操作。但是如果针对一个保存有不同类型对象的聚集采取某种操作该怎么办呢?
粗看上去,这似乎不是什么难题。可是如果需要针对一个包含不同类型元素的聚集采取某种操作,而操作的细节根据元素的类型不同而有所不同时,就会出现必须对元素类型做类型判断的条件转移语句。这个时候,使用访问者模式就是一个值得考虑的解决方案。
访问者模式
访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做"双重分派"。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。
双重分派意味着施加于节点之上的操作是基于访问者和节点本身的数据类型,而不仅仅是其中的一者。

二、访问者模式的结构


访问者模式涉及到抽象访问者角色、具体访问者角色、抽象节点角色、具体节点角色、结构对象角色以及客户端角色。

  • 抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。
  • 抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参量。
  • 具体节点(Node)角色:实现了抽象元素所规定的接受操作。
  • 结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。

三、访问者模式示例代码

抽象访问者(Visitor)角色
public abstract class Visitor
{
	protected String name;
	
	public void setName(String name)
	{
		this.name=name;
	}
	
	public abstract void visit(Apple apple);
	
	public abstract void visit(Book book);
}
具体访问者(ConcreteVisitor)角色
public class Customer extends Visitor
{
	public void visit(Apple apple)
	{
		System.out.println("顾客" + name + "选苹果。");
	}
	
	public void visit(Book book)
	{
		System.out.println("顾客" + name + "买书。");
	}
}
public class Saler extends Visitor
{
	public void visit(Apple apple)
	{
		System.out.println("收银员" + name + "给苹果过秤,然后计算其价格。");
	}
	
	public void visit(Book book)
	{
		System.out.println("收银员" + name + "直接计算书的价格。");
	}
}
抽象节点(Node)角色
public interface Product
{
	void accept(Visitor visitor);
}
具体节点(Node)角色
public class Apple implements Product
{
  public void accept(Visitor visitor)
  {
      visitor.visit(this);
  }	
}
public class Book implements Product
{
  public void accept(Visitor visitor)
  {
      visitor.visit(this);
  }	
}
结构对象(ObiectStructure)角色
import java.util.*;

public class BuyBasket
{
	private ArrayList list=new ArrayList();
	
	public void accept(Visitor visitor)
	{
		Iterator i=list.iterator();
		
		while(i.hasNext())
		{
			((Product)i.next()).accept(visitor);	
		}
	}
	
	public void addProduct(Product product)
	{
		list.add(product);
	}
	
	public void removeProduct(Product product)
	{
		list.remove(product);
	}
}
测试
<?xml version="1.0"?>
<config>
    <className>Saler</className>
</config>
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class XMLUtil
{
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
	public static Object getBean()
	{
		try
		{
			//创建文档对象
			DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = dFactory.newDocumentBuilder();
			Document doc;							
			doc = builder.parse(new File("config.xml")); 
		
			//获取包含类名的文本节点
			NodeList nl = doc.getElementsByTagName("className");
            Node classNode=nl.item(0).getFirstChild();
            String cName=classNode.getNodeValue();
            
            //通过类名生成实例对象并将其返回
            Class c=Class.forName(cName);
	  	    Object obj=c.newInstance();
            return obj;
           }   
           	catch(Exception e)
           	{
           		e.printStackTrace();
           		return null;
           	}
		}
}
public class Client
{
	public static void main(String a[])
	{
		Product b1=new Book();
		Product b2=new Book();
		Product a1=new Apple();
		Visitor visitor;
		
        BuyBasket basket=new BuyBasket();
        basket.addProduct(b1);
        basket.addProduct(b2);
        basket.addProduct(a1);
        
        visitor=(Visitor)XMLUtil.getBean();
        
        visitor.setName("张三");
        	
        basket.accept(visitor);
	}
}

四、访问者模式分析

  1. 访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。
  2. 访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。
  3. 相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性。

五、访问者模式优缺点

访问者模式的优点
  1. 使得增加新的访问操作变得很容易。
  2. 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中。
  3. 可以跨过类的等级结构访问属于不同的等级结构的元素类。
  4. 让用户能够在不修改现有类层次结构的情况下,定义该类层次结构的操作。
访问者模式的缺点
  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”的要求。
  2. 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

六、访问者模式适用环境

在以下情况下可以使用访问者模式:
  1. 一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
  3. 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

七、访问者模式扩展

与其他模式联用
  • 由于访问者模式需要对对象结构进行操作,而对象结构本身是一个元素对象的集合,因此访问者模式经常需要与迭代器模式联用,在对象结构中使用迭代器来遍历元素对象。
  • 在访问者模式中,元素对象可能存在容器对象和叶子对象,因此可以结合组合模式来进行设计。
倾斜的“开闭原则”
  • 访问者模式以一种倾斜的方式支持“开闭原则”,增加新的访问者方便,但是增加新的元素很困难。

八、访问者模式应用

(1) 在一些编译器的设计中运用了访问者模式,程序代码是被访问的对象,它包括变量定义、变量赋值、逻辑运算、算术运算等语句,编译器需要对代码进行分析,如检查变量是否定义、变量是否赋值、算术运算是否合法等,可以将不同的操作封装在不同的类中,如检查变量定义的类、检查变量赋值的类、检查算术运算是否合法的类,这些类就是具体访问者,可以访问程序代码中不同类型的语句。在编译过程中除了代码分析外,还包含代码优化、空间分配和代码生成等部分,也可以将每一个不同编译阶段的操作封装到了跟该阶段有关的一个访问者类中。
(2) 在常用的Java XML处理技术DOM4J中,可以通过访问者模式的方式来读取并解析XML文档,VisitorSupport是DOM4J提供的Visitor接口的默认适配器,具体访问者只需继承VisitorSupport类即可。
public class MyVisitor extends VisitorSupport 
{
    public void visit(Element element)
    {
        System.out.println(element.getName());
    }
    public void visit(Attribute attr)
    {
        System.out.println(attr.getName());
    }
}

九、参考资料

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics