《Java设计模式》之访问者模式

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

分派的概念

  变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type);而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。比如

[java] view plaincopyprint?

  1. List list = null;
  2. list = new ArrayList();
List list = null;
list = new ArrayList();

  声明了一个变量list,它的静态类型(也叫明显类型)是List,而它的实际类型是ArrayList。

  根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派动态分派

  静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。

  动态分派(Dynamic Dispatch)发生在运行时期,动态分派动态地置换掉某个方法。

 静态分派

  Java通过方法重载支持静态分派。用墨子骑马的故事作为例子,墨子可以骑白马或者黑马。墨子与白马、黑马和马的类图如下所示:

  在这个系统中,墨子由Mozi类代表

[java] view plaincopyprint?

  1. package com.bankht.Visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:41:24
  5. *
  6. * @类说明 :在这个系统中,墨子由Mozi类代表
  7. */
  8. public class Mozi {
  9. public void ride(Horse h) {
  10. System.out.println("骑马");
  11. }
  12. public void ride(WhiteHorse wh) {
  13. System.out.println("骑白马");
  14. }
  15. public void ride(BlackHorse bh) {
  16. System.out.println("骑黑马");
  17. }
  18. public static void main(String[] args) {
  19. Horse wh = new WhiteHorse();
  20. Horse bh = new BlackHorse();
  21. Mozi mozi = new Mozi();
  22. mozi.ride(wh);
  23. mozi.ride(bh);
  24. }
  25. }
package com.bankht.Visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:41:24
 *
 * @类说明 :在这个系统中,墨子由Mozi类代表
 */
public class Mozi {

	public void ride(Horse h) {
		System.out.println("骑马");
	}

	public void ride(WhiteHorse wh) {
		System.out.println("骑白马");
	}

	public void ride(BlackHorse bh) {
		System.out.println("骑黑马");
	}

	public static void main(String[] args) {
		Horse wh = new WhiteHorse();
		Horse bh = new BlackHorse();
		Mozi mozi = new Mozi();
		mozi.ride(wh);
		mozi.ride(bh);
	}

}

  显然,Mozi类的ride()方法是由三个方法重载而成的。这三个方法分别接受马(Horse)、白马(WhiteHorse)、黑马(BlackHorse)等类型的参数。

  那么在运行时,程序会打印出什么结果呢?结果是程序会打印出相同的两行“骑马”。换言之,墨子发现他所骑的都是马。

  为什么呢?两次对ride()方法的调用传入的是不同的参数,也就是wh和bh。它们虽然具有不同的真实类型,但是它们的静态类型都是一样的,均是Horse类型。

  重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。

 动态分派

  Java通过方法的重写支持动态分派。用马吃草的故事作为例子,代码如下所示:

[java] view plaincopyprint?

  1. package com.bankht.Visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:41:46
  5. *
  6. * @类说明 :Java通过方法的重写支持动态分派。用马吃草的故事作为例子
  7. */
  8. public class Horse {
  9. public void eat() {
  10. System.out.println("马吃草");
  11. }
  12. }
package com.bankht.Visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:41:46
 *
 * @类说明 :Java通过方法的重写支持动态分派。用马吃草的故事作为例子
 */
public class Horse {

	public void eat() {
		System.out.println("马吃草");
	}

}

[java] view plaincopyprint?

  1. package com.bankht.Visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:43:15
  5. *
  6. * @类说明 :
  7. */
  8. public class WhiteHorse extends Horse {
  9. @Override
  10. public void eat() {
  11. System.out.println("白马吃草");
  12. }
  13. }
package com.bankht.Visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:43:15
 *
 * @类说明 :
 */
public class WhiteHorse extends Horse {

	@Override
	public void eat() {
		System.out.println("白马吃草");
	}
}

[java] view plaincopyprint?

  1. package com.bankht.Visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:42:08
  5. *
  6. * @类说明 :
  7. */
  8. public class BlackHorse extends Horse {
  9. @Override
  10. public void eat() {
  11. System.out.println("黑马吃草");
  12. }
  13. }
package com.bankht.Visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:42:08
 *
 * @类说明 :
 */
public class BlackHorse extends Horse {

	@Override
	public void eat() {
		System.out.println("黑马吃草");
	}
}

[java] view plaincopyprint?

  1. package com.bankht.Visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:42:34
  5. *
  6. * @类说明 :
  7. */
  8. public class Client {
  9. public static void main(String[] args) {
  10. Horse h = new BlackHorse();
  11. h.eat();
  12. }
  13. }
package com.bankht.Visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:42:34
 *
 * @类说明 :
 */
public class Client {

	public static void main(String[] args) {
		Horse h = new BlackHorse();
		h.eat();
	}

}

运行一下:

[html] view plaincopyprint?

  1. 黑马吃草
黑马吃草

  变量h的静态类型是Horse,而真实类型是BlackHorse。如果上面最后一行的eat()方法调用的是BlackHorse类的eat()方法,那么上面打印的就是“黑马吃草”;相反,如果上面的eat()方法调用的是Horse类的eat()方法,那么打印的就是“马吃草”。

  所以,问题的核心就是Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。这样一来,上面最后一行的eat()方法调用的是BlackHorse类的eat()方法,打印的是“黑马吃草”。

 分派的类型

  一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参数统称做方法的宗量。比如下面例子中的Test类

[java] view plaincopyprint?

  1. public class Test {
  2. public void print(String str){
  3. System.out.println(str);
  4. }
  5. }
public class Test {

    public void print(String str){
        System.out.println(str);
    }
}

  在上面的类中,print()方法属于Test对象,所以它的接收者也就是Test对象了。print()方法有一个参数是str,它的类型是String。

  根据分派可以基于多少种宗量,可以将面向对象的语言划分为单分派语言(Uni-Dispatch)和多分派语言(Multi-Dispatch)。单分派语言根据一个宗量的类型进行对方法的选择,多分派语言根据多于一个的宗量的类型对方法进行选择。

  C++和Java均是单分派语言,多分派语言的例子包括CLOS和Cecil。按照这样的区分,Java就是动态的单分派语言,因为这种语言的动态分派仅仅会考虑到方法的接收者的类型,同时又是静态的多分派语言,因为这种语言对重载方法的分派会考虑到方法的接收者的类型以及方法的所有参数的类型。

  在一个支持动态单分派的语言里面,有两个条件决定了一个请求会调用哪一个操作:一是请求的名字,而是接收者的真实类型。单分派限制了方法的选择过程,使得只有一个宗量可以被考虑到,这个宗量通常就是方法的接收者。在Java语言里面,如果一个操作是作用于某个类型不明的对象上面,那么对这个对象的真实类型测试仅会发生一次,这就是动态的单分派的特征。

 双重分派

  一个方法根据两个宗量的类型来决定执行不同的代码,这就是“双重分派”。Java语言不支持动态的多分派,也就意味着Java不支持动态的双分派。但是通过使用设计模式,也可以在Java语言里实现动态的双重分派。

  在Java中可以通过两次方法调用来达到两次分派的目的。类图如下所示:

  在图中有两个对象,左边的叫做West,右边的叫做East。现在West对象首先调用East对象的goEast()方法,并将它自己传入。在East对象被调用时,立即根据传入的参数知道了调用者是谁,于是反过来调用“调用者”对象的goWest()方法。通过两次调用将程序控制权轮番交给两个对象,其时序图如下所示:

  这样就出现了两次方法调用,程序控制权被两个对象像传球一样,首先由West对象传给了East对象,然后又被返传给了West对象。

  但是仅仅返传了一下球,并不能解决双重分派的问题。关键是怎样利用这两次调用,以及Java语言的动态单分派功能,使得在这种传球的过程中,能够触发两次单分派。

  动态单分派在Java语言中是在子类重写父类的方法时发生的。换言之,West和East都必须分别置身于自己的类型等级结构中,如下图所示:

  源代码

  West类

[java] view plaincopyprint?

  1. public abstract class West {
  2. public abstract void goWest1(SubEast1 east);
  3. public abstract void goWest2(SubEast2 east);
  4. }
public abstract class West {

    public abstract void goWest1(SubEast1 east);

    public abstract void goWest2(SubEast2 east);
}

  SubWest1类

[java] view plaincopyprint?

  1. public class SubWest1 extends West{
  2. @Override
  3. public void goWest1(SubEast1 east) {
  4. System.out.println("SubWest1 + " + east.myName1());
  5. }
  6. @Override
  7. public void goWest2(SubEast2 east) {
  8. System.out.println("SubWest1 + " + east.myName2());
  9. }
  10. }
public class SubWest1 extends West{

    @Override
    public void goWest1(SubEast1 east) {

        System.out.println("SubWest1 + " + east.myName1());
    }

    @Override
    public void goWest2(SubEast2 east) {

        System.out.println("SubWest1 + " + east.myName2());
    }
}

 SubWest2类

[java] view plaincopyprint?

  1. public class SubWest2 extends West{
  2. @Override
  3. public void goWest1(SubEast1 east) {
  4. System.out.println("SubWest2 + " + east.myName1());
  5. }
  6. @Override
  7. public void goWest2(SubEast2 east) {
  8. System.out.println("SubWest2 + " + east.myName2());
  9. }
  10. }
public class SubWest2 extends West{
    @Override
    public void goWest1(SubEast1 east) {

        System.out.println("SubWest2 + " + east.myName1());
    }

    @Override
    public void goWest2(SubEast2 east) {

        System.out.println("SubWest2 + " + east.myName2());
    }
}

 East类

[java] view plaincopyprint?

  1. public abstract class East {
  2. public abstract void goEast(West west);
  3. }
public abstract class East {

    public abstract void goEast(West west);
}

  SubEast1类

[java] view plaincopyprint?

  1. public class SubEast1 extends East{
  2. @Override
  3. public void goEast(West west) {
  4. west.goWest1(this);
  5. }
  6. public String myName1(){
  7. return "SubEast1";
  8. }
  9. }
public class SubEast1 extends East{
    @Override
    public void goEast(West west) {
        west.goWest1(this);
    }

    public String myName1(){
        return "SubEast1";
    }
}

  SubEast2类

[java] view plaincopyprint?

  1. public class SubEast2 extends East{
  2. @Override
  3. public void goEast(West west) {
  4. west.goWest2(this);
  5. }
  6. public String myName2(){
  7. return "SubEast2";
  8. }
  9. }
public class SubEast2 extends East{
    @Override
    public void goEast(West west) {
        west.goWest2(this);
    }

    public String myName2(){
        return "SubEast2";
    }
}

  客户端类

[java] view plaincopyprint?

  1. public class Client {
  2. public static void main(String[] args) {
  3. //组合1
  4. East east = new SubEast1();
  5. West west = new SubWest1();
  6. east.goEast(west);
  7. //组合2
  8. east = new SubEast1();
  9. west = new SubWest2();
  10. east.goEast(west);
  11. }
  12. }
public class Client {

    public static void main(String[] args) {
        //组合1
        East east = new SubEast1();
        West west = new SubWest1();
        east.goEast(west);
        //组合2
        east = new SubEast1();
        west = new SubWest2();
        east.goEast(west);
    }

}

运行结果如下

--------------------------------------------------------------------------------  

SubWest1 + SubEast1

SubWest2 + SubEast1

--------------------------------------------------------------------------------

  系统运行时,会首先创建SubWest1和SubEast1对象,然后客户端调用SubEast1的goEast()方法,并将SubWest1对象传入。由于SubEast1对象重写了其超类East的goEast()方法,因此,这个时候就发生了一次动态的单分派。当SubEast1对象接到调用时,会从参数中得到SubWest1对象,所以它就立即调用这个对象的goWest1()方法,并将自己传入。由于SubEast1对象有权选择调用哪一个对象,因此,在此时又进行一次动态的方法分派。

  这个时候SubWest1对象就得到了SubEast1对象。通过调用这个对象myName1()方法,就可以打印出自己的名字和SubEast对象的名字,其时序图如下所示:

  由于这两个名字一个来自East等级结构,另一个来自West等级结构中,因此,它们的组合式是动态决定的。这就是动态双重分派的实现机制。

访问者模式的结构

  访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。访问者模式的简略图如下所示:

数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。访问者模式的示意性类图如下所示:

  访问者模式涉及到的角色如下:

  ●  抽象访问者(Visitor)角色:声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口。

  ●  具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。

  ●  抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参数。

  ●  具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。

  ●  结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如List或Set。

  源代码

  可以看到,抽象访问者角色为每一个具体节点都准备了一个访问操作。由于有两个节点,因此,对应就有两个访问操作。

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:50:06
  5. *
  6. * @类说明 :可以看到,抽象访问者角色为每一个具体节点都准备了一个访问操作。由于有两个节点,因此,对应就有两个访问操作。
  7. */
  8. public interface Visitor {
  9. /**
  10. * 对应于NodeA的访问操作
  11. */
  12. public void visit(NodeA node);
  13. /**
  14. * 对应于NodeB的访问操作
  15. */
  16. public void visit(NodeB node);
  17. }
package com.bankht.Visitor.visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:50:06
 *
 * @类说明 :可以看到,抽象访问者角色为每一个具体节点都准备了一个访问操作。由于有两个节点,因此,对应就有两个访问操作。
 */
public interface Visitor {
	/**
	 * 对应于NodeA的访问操作
	 */
	public void visit(NodeA node);

	/**
	 * 对应于NodeB的访问操作
	 */
	public void visit(NodeB node);
}

  具体访问者VisitorA类

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:58:04
  5. *
  6. * @类说明 :
  7. */
  8. public class VisitorA implements Visitor {
  9. /**
  10. * 对应于NodeA的访问操作
  11. */
  12. @Override
  13. public void visit(NodeA node) {
  14. System.out.println("VisitorA:" + node.operationA());
  15. }
  16. /**
  17. * 对应于NodeB的访问操作
  18. */
  19. @Override
  20. public void visit(NodeB node) {
  21. System.out.println("VisitorA:" + node.operationB());
  22. }
  23. }
package com.bankht.Visitor.visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:58:04
 *
 * @类说明 :
 */
public class VisitorA implements Visitor {
	/**
	 * 对应于NodeA的访问操作
	 */
	@Override
	public void visit(NodeA node) {
		System.out.println("VisitorA:" + node.operationA());
	}

	/**
	 * 对应于NodeB的访问操作
	 */
	@Override
	public void visit(NodeB node) {
		System.out.println("VisitorA:" + node.operationB());
	}

}

  具体访问者VisitorB类

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:58:21
  5. *
  6. * @类说明 :
  7. */
  8. public class VisitorB implements Visitor {
  9. /**
  10. * 对应于NodeA的访问操作
  11. */
  12. @Override
  13. public void visit(NodeA node) {
  14. System.out.println("VisitorB:" + node.operationA());
  15. }
  16. /**
  17. * 对应于NodeB的访问操作
  18. */
  19. @Override
  20. public void visit(NodeB node) {
  21. System.out.println("VisitorB:" + node.operationB());
  22. }
  23. }
package com.bankht.Visitor.visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:58:21
 *
 * @类说明 :
 */
public class VisitorB implements Visitor {
	/**
	 * 对应于NodeA的访问操作
	 */
	@Override
	public void visit(NodeA node) {
		System.out.println("VisitorB:" + node.operationA());
	}

	/**
	 * 对应于NodeB的访问操作
	 */
	@Override
	public void visit(NodeB node) {
		System.out.println("VisitorB:" + node.operationB());
	}

}

  抽象节点类

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:58:57
  5. *
  6. * @类说明 :
  7. */
  8. public abstract class Node {
  9. /**
  10. * 接受操作
  11. */
  12. public abstract void accept(Visitor visitor);
  13. }
package com.bankht.Visitor.visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:58:57
 *
 * @类说明 :
 */
public abstract class Node {
	/**
	 * 接受操作
	 */
	public abstract void accept(Visitor visitor);
}

 具体节点类NodeA

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:59:45
  5. *
  6. * @类说明 :
  7. */
  8. public class NodeA extends Node {
  9. /**
  10. * 接受操作
  11. */
  12. @Override
  13. public void accept(Visitor visitor) {
  14. visitor.visit(this);
  15. }
  16. /**
  17. * NodeA特有的方法
  18. */
  19. public String operationA() {
  20. return "NodeA";
  21. }
  22. }
package com.bankht.Visitor.visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:59:45
 *
 * @类说明 :
 */
public class NodeA extends Node {
	/**
	 * 接受操作
	 */
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

	/**
	 * NodeA特有的方法
	 */
	public String operationA() {
		return "NodeA";
	}

}

  具体节点类NodeB

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午10:59:59
  5. *
  6. * @类说明 :
  7. */
  8. public class NodeB extends Node {
  9. /**
  10. * 接受方法
  11. */
  12. @Override
  13. public void accept(Visitor visitor) {
  14. visitor.visit(this);
  15. }
  16. /**
  17. * NodeB特有的方法
  18. */
  19. public String operationB() {
  20. return "NodeB";
  21. }
  22. }
package com.bankht.Visitor.visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午10:59:59
 *
 * @类说明 :
 */
public class NodeB extends Node {
	/**
	 * 接受方法
	 */
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

	/**
	 * NodeB特有的方法
	 */
	public String operationB() {
		return "NodeB";
	}
}

  结构对象角色类,这个结构对象角色持有一个聚集,并向外界提供add()方法作为对聚集的管理操作。通过调用这个方法,可以动态地增加一个新的节点。

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. /**
  5. * @author: 特种兵—AK47
  6. * @创建时间:2012-7-3 上午11:00:15
  7. *
  8. * @类说明 :
  9. */
  10. public class ObjectStructure {
  11. private List<Node> nodes = new ArrayList<Node>();
  12. /**
  13. * 执行方法操作
  14. */
  15. public void action(Visitor visitor) {
  16. for (Node node : nodes) {
  17. node.accept(visitor);
  18. }
  19. }
  20. /**
  21. * 添加一个新元素
  22. */
  23. public void add(Node node) {
  24. nodes.add(node);
  25. }
  26. }
package com.bankht.Visitor.visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午11:00:15
 *
 * @类说明 :
 */
public class ObjectStructure {

	private List<Node> nodes = new ArrayList<Node>();

	/**
	 * 执行方法操作
	 */
	public void action(Visitor visitor) {

		for (Node node : nodes) {
			node.accept(visitor);
		}

	}

	/**
	 * 添加一个新元素
	 */
	public void add(Node node) {
		nodes.add(node);
	}
}

  客户端类

[java] view plaincopyprint?

  1. package com.bankht.Visitor.visitor;
  2. /**
  3. * @author: 特种兵—AK47
  4. * @创建时间:2012-7-3 上午11:00:29
  5. *
  6. * @类说明 :
  7. */
  8. public class Client {
  9. public static void main(String[] args) {
  10. // 创建一个结构对象
  11. ObjectStructure os = new ObjectStructure();
  12. // 给结构增加一个节点
  13. os.add(new NodeA());
  14. // 给结构增加一个节点
  15. os.add(new NodeB());
  16. // 创建一个访问者
  17. Visitor visitor = new VisitorA();
  18. os.action(visitor);
  19. }
  20. }
package com.bankht.Visitor.visitor;

/**
 * @author: 特种兵—AK47
 * @创建时间:2012-7-3 上午11:00:29
 *
 * @类说明 :
 */
public class Client {

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

}

  虽然在这个示意性的实现里并没有出现一个复杂的具有多个树枝节点的对象树结构,但是,在实际系统中访问者模式通常是用来处理复杂的对象树结构的,而且访问者模式可以用来处理跨越多个等级结构的树结构问题。这正是访问者模式的功能强大之处。

  准备过程时序图

  首先,这个示意性的客户端创建了一个结构对象,然后将一个新的NodeA对象和一个新的NodeB对象传入。

  其次,客户端创建了一个VisitorA对象,并将此对象传给结构对象。

  然后,客户端调用结构对象聚集管理方法,将NodeA和NodeB节点加入到结构对象中去。

  最后,客户端调用结构对象的行动方法action(),启动访问过程。

  访问过程时序图

  

  结构对象会遍历它自己所保存的聚集中的所有节点,在本系统中就是节点NodeA和NodeB。首先NodeA会被访问到,这个访问是由以下的操作组成的:

  (1)NodeA对象的接受方法accept()被调用,并将VisitorA对象本身传入;

  (2)NodeA对象反过来调用VisitorA对象的访问方法,并将NodeA对象本身传入;

  (3)VisitorA对象调用NodeA对象的特有方法operationA()。

  从而就完成了双重分派过程,接着,NodeB会被访问,这个访问的过程和NodeA被访问的过程是一样的,这里不再叙述。

访问者模式的优点

  ●  好的扩展性

  能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  ●  好的复用性

  可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  ●  分离无关行为

  可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

访问者模式的缺点

  ●  对象结构变化很困难

  不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。

  ●  破坏封装

  访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。

  

本文借鉴:http://blog.csdn.net/m13666368773/article/details/7711326

版权声明:欢迎转载,希望在你转载的同时,添加原文地址,谢谢配合

时间: 2024-08-10 00:06:40

《Java设计模式》之访问者模式的相关文章

JAVA设计模式之 访问者模式【Visitor Pattern】

一.概述 访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作.在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为"对象结构",访问者通过遍历对象结构实现对其中存储的元素的逐个操作.访问者模式是一种对象行为型模式. 二.适用场景 当有多种类型的访问者(或是操作者) 对一组被访问者对象集合(或是对象结构)进行操作(其中对象集合也包含多种类型对象

Java设计模式之访问者模式

本文继续23种设计模式系列之访问者模式. 定义 封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作. class A { public void method1(){ System.out.println("我是A"); } public void method2(B b){ b.showA(this); } } class B { public void showA(A a){ a.method1(); } } 看一下在类A中,方法m

JAVA设计模式之代理模式

学编程吧JAVA设计模式之代理模式发布了,欢迎通过xuebiancheng8.com来访问 一.概述 给某一个对象提供一个代理,并由代理对象来完成对原对象的访问.代理模式是一种对象结构型模式. 二.适用场景 当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口. 三.UML类图 四.参与者 1.接口类:Subject 它声明了真实访问者和代理访问者的共同接口,客户端通常需要针对接口角色进行编程. 2.代理类

浅析JAVA设计模式之工厂模式(一)

1 工厂模式简介 工厂模式的定义:简单地说,用来实例化对象,代替new操作. 工厂模式专门负责将大量有共同接口的类实例化.工作模式可以动态决定将哪一个类实例化,不用先知道每次要实例化哪一个类. 工厂模式可以分一下三种形态: 简单工厂 (Simple Factory)模式:又称静态工厂模式(StaticFactory). 工厂方法 (Factroy Method)模式:又称多态性工厂模式(Polymorphic Factory). 抽象工厂 (Abstract Factroy)模式:又称工具箱模式

浅析JAVA设计模式之工厂模式(二)

1 工厂方法模式简介 工厂方法 (Factroy Method) 模式:又称多态性工厂模式(Polymorphic Factory),在这种模式中,核心工厂不再是一个具体的类,而是一个抽象工厂,提供具体工厂实现的接口,具体创建产品交由子工厂去做,抽象工厂不涉及任何产品被实例化的细节.而不同等级的产品,就对应一个不同等级的工厂,如下图. 图1 1.1工厂方法模式(多态性工厂模式): 工厂方法模式有三个角色: 1. 抽象产品接口 2. 具体产品类 3. 抽象工厂接口 4.具体工厂类. 1.2工厂方法

JAVA设计模式(3)----代理模式

1.  什么是代理模式?Proxy Pattern 代理模式定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 通俗的讲,代理模式就是我很忙没空理你,你要想找我可以先找我的代理人,代理人和被代理人继承同一个接口.代理人虽然不能干活,但是被代理的人可以干活. 这个例子中有水浒传中的这么几个人:名垂青史的潘金莲,王婆,西门大官人.西门庆想要找潘金莲,需要找王婆做代理.首先定义一个接口:Kin

java设计模式------装饰着模式

java设计模式-------装饰者模式 装饰者模式 Decorator模式(别名Wrapper):动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案.主要有组件(components)和装饰器(Decorator)组成.要求components和Decorator实现相同的接口或者抽象类(具体类的局限性太大). 设计原则.模式特点.适用性 - 1. 多用组合,少用继承. 利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为.然而,如果能够利用

浅析JAVA设计模式之工厂模式(三)

在阅读本文之前,请先阅读(一)和(二)中的简单工厂模式和工厂方法模式. 1抽象工厂模式简介 抽象工厂 (Abstract Factroy) 模式:工具箱模式(kit).抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态,如下图. 图1.1 上图左边有一个工厂类的等级结构,右边有两个不同的产品等级结构,分别是产品A的等级结构和产品B的等级结构,工厂1和工厂2分别负责不同一个产品等级,同一个产品族的产品的生产.又例如下图: 图1.2 上图表示的是Button和Text两个不同产品的等级

JAVA设计模式之 状态模式【State Pattern】

一.概述 当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式.状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化.状态模式是一种对象行为型模式. 二.适用场景 用于解决系统中复杂对象的多种状态转换以及不同状态下行为的封装问题.简单说就是处理对象的多种状态及其相互转换. 三.UML类图 四.参与者 1>.AbstractState(抽象状态类): 在抽象状态类中定义申明了不同状态下的行为抽象方法,而由子类

PHP教程:掌握php设计模式之访问者模式

PHP教程:掌握php设计模式之访问者模式 这篇文章主要帮助大家轻松掌握php设计模式之访问者模式,感兴趣的小伙伴们可以参考一下 访问者模式解决的问题 在我们的代码编写过程当中,经常需要对一些类似的对象添加一些的代码,我们以一个计算机对象打印组成部分为例来看下: /** * 抽象基类 */ abstract class Unit { /** *获取名称 */ abstract public function getName(); } /** * Cpu类 */ class Cpu extends