`

六十一:访问者模式

阅读更多
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变.

一:访问者模式需要解决的问题
聚集是大多数的系统都要处理的一种容器对象,它保存了对其他对象的引用。相信大多数读者都有处理聚集的经验,但是大家处理过的大多数聚集恐怕都是同类对象的聚集。换言之,在聚集上采取的操作都是一些针对同类型对象的同类操作,而迭代子就是为这种情况准备的设计模式.下面看一个例子:
public void print(Collection collection){
Iterator iterator = (Iterator) collection.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next().toString());
}
}

那么很多人没有考虑过的问题就是,如何针对一个保存有不同类型的对象的聚集采取某种操作呢?仍以上面的print方法为例子,如果collection聚集中的元素有可能还是聚集,那么调用聚集的toString就没有意义了,应该调用它的内部元素的toString方法,换言之,上面的方法应该改写为:
public void print(Collection collection){
Iterator iterator = (Iterator) collection.iterator();
while(iterator.hasNext()){
Object o = iterator.next();
if(o instanceof Collection){
print((Collection)o);
}else{
System.out.println(iterator.next().toString());
}
}
}
但是这还没完,如果这个操作对不同的类型的元素有所不同时怎么办?比如系统要求打印字符串时加上单引号,打印Double类型的数据时,在数据后面加上D,在打印Float类型的数据时,在数据后面加上F,这时我们还继续修改print方法:
public void print(Collection collection){
Iterator iterator = (Iterator) collection.iterator();
while(iterator.hasNext()){
Object o = iterator.next();
if(o instanceof Collection){
print((Collection)o);
}else if(o instanceof String){
System.out.println("'" o.toString() "'");
}else if(o instanceof Double){
System.out.println(o.toString() "D");
}else{
System.out.println(iterator.next().toString());
}
}
}


这个条件转移语句变得越来越长,代码也越来越难以维护。换言之,如果需要针对一个包含不同类型的聚集采取某种操作时,而操作的细节根据元素的类型不同而有所不同时,就会出现必须对元素类型做类型判断的条件转移语句(这实际上就是双重分派的实际应用).
这个问题我们就可以用访问者模式来解决.

二:访问者模式
访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于数据结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化.数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作,这样的过程叫做双重要分派.节点调用访问者,将它自己的传入,访问者则将某算法针对此节点执行.它所涉及的角色如下:
(A)抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口,它为每一个具体节点都准备了一个访问操作.
(B)具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口.
(C)抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参量
(D)具体节点(ConcreteNode)角色:实现了抽象元素所规定的接受操作
(E)结构对象(ObjectStructure)角色:可以遍历结构中的所有元素,如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素,如果需要,可以设计成一个复合对象或者一个聚集如List。
下面看示意源码:
package cai.milenfan.basic.test;

public interface Visitor {
//对应NodeA的操作
void visit(NodeA node);
//对应NodeB的操作
  void visit(NodeB node);
}


package cai.milenfan.basic.test;

public class VisitorA implements Visitor{
public void visit(NodeA nodeA) {
System.out.println(nodeA.operationA());
}
public void visit(NodeB nodeB) {
System.out.println(nodeB.operationB());
}
}


package cai.milenfan.basic.test;

public class VisitorB {
public void visit(NodeA nodeA) {
System.out.println(nodeA.operationA());
}
public void visit(NodeB nodeB) {
System.out.println(nodeB.operationB());
}
}


package cai.milenfan.basic.test;
//抽象节点
public abstract class Node {
public abstract void accept(Visitor visitor);
}


package cai.milenfan.basic.test;

public class NodeA extends Node {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "NodeA is visited";
}
}


package cai.milenfan.basic.test;

public class NodeB extends Node {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "NodeB is visited";
}
}



package cai.milenfan.basic.test;

import java.util.Enumeration;
import java.util.Vector;

public class ObjectStructure {
private Vector nodes;
private Node node;
public ObjectStructure() {
nodes = new Vector();
}
public void action(Visitor visitor) {
for (Enumeration e = nodes.elements(); e.hasMoreElements();) {
node = (Node) e.nextElement();
node.accept(visitor);
}
}
public void add(Node node) {
nodes.addElement(node);
}
}


package cai.milenfan.basic.test;

import java.awt.Canvas;

public class Client {
private static ObjectStructure aObjects;
private static Visitor visitor;

static public void main(String[] args) {
//创建一个结构对象
aObjects = new ObjectStructure();
//给结构增加两个节点
aObjects.add(new NodeA());
aObjects.add(new NodeB());
//创建一个新的访问者
visitor = new VisitorA();
//让访问者访问结构
aObjects.action(visitor);
}
}

三:在什么情况下使用访问者模式
有意思的是,在很多情况下不使用设计模式反而会得到一个较好的设计,换言之,每一个设计模式都有其不应当使用的情况.访问者模式仅仅应当在被访问的类结构非常稳定的情况下使用,换言之系统很少出现需要加入新节点的情况.
如果需要加入新节点的情况,那就必须在每一个访问对象里加入一个对应于这个新节点的访问操作,而这是对一个系统的大规模修改,这违背了"开闭原则".总之,如果系统的数据结构是频繁变化的,则不适用访问者模式.它的优点有:
(a)使得增加新的操作变得容易
(b)访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个节点类中.
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics