Visitor Pattern 和 double-dispatch

Override VS. Overload

    Simple Polymorphism (Override) :the object whose method is called is decided run-time. 
    multi- polymorphism (Overload):the object which method is called  is decided upon the type of
the argument.

upcast is implicit, 向上转型是隐式的, downcast是显示的。

也就是说当你说正方形是形状的时候,是可以的。但是当你说形状是正方形的时候,你必须显示的用括号转型(square)shapeA

如果不幸,此时的形状是圆形的话,it will be crash at runtime, and give you java.lang.ClassCastException 

看这样一个例子:

public class ColoredHorses {
	public void display(Horse h) {	System.out.println("Horse:" + h.message());	}//哪个message方法被调用,这个要看运行时的对象了
	public void display(WhiteHorse wh) {	System.out.println("WhiteHorse:" + wh.message());	}
	public void display(BlackHorse bh) {	System.out.println("BlackHorse:" + bh.message());	}

	public static void main(String[] args) {
		ColoredHorses test = new ColoredHorses();
		Horse h1 = new WhiteHorse(); //h1 被申明为Horse,但是指向WhiteHorse对象
		Horse h2 = new BlackHorse();
		WhiteHorse wh = new WhiteHorse();
		BlackHorse bh = new BlackHorse();

		test.display(h1);//哪个display方法被调用呢?因为type是Horse,所以是display(Horse h)
		test.display(h2);//如果没有display(Horse),将会报编译错误,除非你显示转型到一个子类如(BlackHorse)h2
		test.display(wh);//即使没有display(WhiteHorse)也不会有编译错,因为可以隐式upcast到Horse,然后调用display(Horse)
		test.display(bh);
	}
}

class Horse				 {	protected String message()	 { return "I am a horse";	};	};

class WhiteHorse extends Horse {	protected String message() 	{ return "I am a white horse";	};	};

class BlackHorse extends Horse {	protected String message() 	{ return "I am a black horse";	};	};

其输出是什么呢?

Horse:I am a white horse
Horse:I am a black horse
WhiteHorse:I am a white horse
BlackHorse:I am a black horse

Visitor Pattern

访问者模式,顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。

  《设计模式》一书对于访问者模式给出的定义为:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。从定义可以看出结构对象是使用访问者模式必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于java中的collection概念了。

  以下是访问者模式的组成结构:

  1) 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。

  2) 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。

  3) 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。

  4) 具体元素角色(Concrete Element):实现由元素角色提供的Accept操作。

  5) 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。

废话少说,直接上例子:

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

public class VistorPattern {

	public static void main(String[] args){
		FlowersCompo flowers = new FlowersCompo();
		flowers.addFlower(new Gladiolus());
		flowers.addFlower(new Runuculus());
		flowers.accept(new BeeVisitor());
	}
}

// 访问者角色

interface Visitor {
	void visit(Gladiolus g);

	void visit(Runuculus r);

	void visit(Chrysanthemum c);
}

// The Flower hierarchy cannot be changed:
// 元素角色

interface Flower {
	void accept(Visitor v);
}

// 以下三个具体元素角色

class Gladiolus implements Flower {
	public void accept(Visitor v) {
		v.visit(this);
	}
}

class Runuculus implements Flower {
	public void accept(Visitor v) {
		v.visit(this);
	}
}

class Chrysanthemum implements Flower {
	public void accept(Visitor v) {
		v.visit(this);
	}
}

// 对象结构角色(Object Structure)
class FlowersCompo{
	List<Flower> flowers = new ArrayList<Flower>();

	public void addFlower(Flower f){
		flowers.add(f);
	}

	public void removeFlower(Flower f){
		flowers.remove(f);
	}

	public void accept(Visitor v){
		for (Flower f : flowers){
			f.accept(v);
		}
	}
}

// Add the ability to produce a string:
// 实现的具体访问者角色
class StrVisitor implements Visitor {
	String s;

	public String toString() {
		return s;
	}

	public void visit(Gladiolus g) {
		s = "Gladiolus";
	}

	public void visit(Runuculus r) {
		s = "Runuculus";
	}

	public void visit(Chrysanthemum c) {
		s = "Chrysanthemum";
	}
}

// Add the ability to do "Bee" activities:
// 另一个具体访问者角色

class BeeVisitor implements Visitor {
	public void visit(Gladiolus g) {
		System.out.println("Bee and Gladiolus");
	}

	public void visit(Runuculus r) {
		System.out.println("Bee and Runuculus");
	}

	public void visit(Chrysanthemum c) {
		System.out.println("Bee and Chrysanthemum");
	}
}

一种典型的使用Visitor的场景是,有一组数据,需要有不同的展现形式,这样当对象结构发生改变时,我们只需要改变Object Structure对象,增加或减少里面的Element。

当需要一个新的展现形式时,只需要实现一个新的Visitor。

因为容易发生变化的部分(对象结构,展现形式)得到了封装和解耦,并且可以独立的扩展,整个设计具有比较好的弹性。

不用accept(),用Visitor直接去visit() 元素对象,可以吗?

当然是可以的,而且这样还省去了Element对Visitor的依赖,进一步降低了耦合,我们把上面的程序改成不用accept 的试试

将Object Structure 修改如下:

class FlowersCompo{
	List<Flower> flowers = new ArrayList<Flower>();

	public void addFlower(Flower f){
		flowers.add(f);
	}

	public void removeFlower(Flower f){
		flowers.remove(f);
	}

	public void accept(Visitor v){
		for (Flower f : flowers){
			// f.accept(v);
			v.visit(f); // 用Visitor直接去访问Element, 编译器报错了
		}
	}
}

改完程序后,发现编译器报错了,提示没有定义visit(Flower)的方法,根据上面关于override, overload 的阐述,在overload 中which method is called 是在编译时静态绑定的,查看一下,我们虽然定义了 visit(Gladiolus g),visit(Runuculus r) ,visit(Chrysanthemum c)
但是没有定义visit(Flower)方法

那就在Visitor中定义上呗:

interface Visitor {
	void visit(Flower f);//增加对Flower接口visit的功能

	void visit(Gladiolus g);

	void visit(Runuculus r);

	void visit(Chrysanthemum c);
}

接口发生了改变,因此每个ConcreteVisitor都要实现这个visit(Flower),为了在Runtime时区分真正的对象是什么,BeeVisitor大概要这样写:

class BeeVisitor implements Visitor {

	public void visit (Flower f){	// 通过instanceof来判断运行时对象,并调用被overload的具体方法
		if (f instanceof Gladiolus){
			visit((Gladiolus)f);
		}
		else if (f instanceof Runuculus){
			visit((Runuculus)f);
		}
		else if (f instanceof Chrysanthemum){
			visit((Chrysanthemum)f);
		}
	}

	public void visit(Gladiolus g) {
		System.out.println("Bee and Gladiolus");
	}

	public void visit(Runuculus r) {
		System.out.println("Bee and Runuculus");
	}

	public void visit(Chrysanthemum c) {
		System.out.println("Bee and Chrysanthemum");
	}
}

如果这样修改,每一个Visitor实现都要重写同样的visit(Flower)代码,做个改动,把visit(Flower)放到一个抽象方法中去,下面是去掉accept后的Visitor

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

public class VistorPattern {

	public static void main(String[] args){
		FlowersCompo flowers = new FlowersCompo();
		flowers.addFlower(new Gladiolus());
		flowers.addFlower(new Runuculus());
		flowers.accept(new BeeVisitor());
	}
}

// 访问者角色

interface Visitor {
	public void visit(Flower f);

	public void visit(Gladiolus g);

	public void visit(Runuculus r);

	public void visit(Chrysanthemum c);
}

abstract class AbstractVisitor implements Visitor {
	public void visit (Flower f){
		if (f instanceof Gladiolus){
			visit((Gladiolus)f);
		}
		else if (f instanceof Runuculus){
			visit((Runuculus)f);
		}
		else if (f instanceof Chrysanthemum){
			visit((Chrysanthemum)f);
		}
	}
}

// The Flower hierarchy cannot be changed:
// 元素角色

interface Flower {
}

// 以下三个具体元素角色

class Gladiolus implements Flower {}

class Runuculus implements Flower {}

class Chrysanthemum implements Flower {}

// 对象结构角色(Object Structure)
class FlowersCompo{
	List<Flower> flowers = new ArrayList<Flower>();

	public void addFlower(Flower f){
		flowers.add(f);
	}

	public void removeFlower(Flower f){
		flowers.remove(f);
	}

	public void accept(Visitor v){
		for (Flower f : flowers){
			// f.accept(v);
			v.visit(f);
		}
	}
}

// Add the ability to produce a string:
// 实现的具体访问者角色
class StrVisitor extends AbstractVisitor {
	String s;

	public String toString() {
		return s;
	}

	public void visit(Gladiolus g) {
		s = "Gladiolus";
	}

	public void visit(Runuculus r) {
		s = "Runuculus";
	}

	public void visit(Chrysanthemum c) {
		s = "Chrysanthemum";
	}
}

// Add the ability to do "Bee" activities:
// 另一个具体访问者角色

class BeeVisitor extends AbstractVisitor {

	public void visit(Gladiolus g) {
		System.out.println("Bee and Gladiolus");
	}

	public void visit(Runuculus r) {
		System.out.println("Bee and Runuculus");
	}

	public void visit(Chrysanthemum c) {
		System.out.println("Bee and Chrysanthemum");
	}
}

再回头看看,在正统的Visitor模式中为什么要用accept(),其实这是一个double-dispatch 模式,仔细观察下concreteElement的accept实现

class Chrysanthemum implements Flower {
	public void accept(Visitor v) {
		v.visit(this);//用visit(this)解决了visit(Flower)问题
	}
}

既然你不能在runtime决定调用哪个visit()方法,那么我把你请进来,然后再让你调用我,其实就是用一个技巧解决了我们必须要用instanceof 做区分的问题。

在我看来,使不使用accept()都是Visitor模式,关键是要理解其要义和应该使用的场景。

其核心都是面向对象的一些基本设计原则:封装变化原则,Open-Close原则 等等

注意:

在使用Visitor时,被visit的类结构应该尽量稳定,因为新加一个被visit对象就意味着你要修改Visit的interface及所有concreteVisitor。

或者使用组合模式来组装需要被visit的对象,也是个不错的选择。

时间: 2024-11-17 20:24:07

Visitor Pattern 和 double-dispatch的相关文章

第23章 访问者模式(Visitor Pattern)

原文 第23章 访问者模式(Visitor Pattern) 访问者模式        导读:访问者模式是我个人认为所有行为模式中最为复杂的一种模式了,这个模式可能看一遍会看不懂,我也翻了好几个例子,依然不能很好的理解访问者模式的核心.下面这个例子是来源于大话设计模式中的例子,稍作了修改!后续如果我有更好的例子或者想法我会对本章进行完善.       概述:       一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作(神一般的语言).    

乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)

原文:乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)[索引页][源码下载] 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) 作者:webabcd 介绍 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 示例 有一个Message实体类,某些对象对它的操作有Insert()和Get()方法,现在要针对其中某一方法进行操作. MessageModel using System;using

.Net设计模式实例之访问者模式(Visitor Pattern)

一.访问者模式简介(Brief Introduction) 表示一个作用于某对象结构中的元素操作.它使你可以在不改变各元素类的前提下定义 作用于这些元素的新操作,它把数据结构和作用于结构上的操作之间的耦合性解脱开,使的 操作结合可以相对自由地演化.优点是增加新的操作很容易,因为增加一个新的操作就意味 着增加一个新的访问者,访问者模式将有关的行为集中到一个访问对象中. 二.解决的问题(What To Solve) 访问者模式的目的是要把处理从数据结构分离出来.如果系统有比较稳定的数据结构, 又有易

访问器范式

接下来,让我们思考如何将具有完全不同目标的一个设计范式应用到垃圾归类系统. 对这个范式,我们不再关心在系统中加入新型Trash时的优化.事实上,这个范式使新型Trash的添加显得更加复杂.假定我们有一个基本类结构,它是固定不变的:它或许来自另一个开发者或公司,我们无权对那个结构进行任何修改.然而,我们又希望在那个结构里加入新的多形性方法.这意味着我们一般必须在基础类的接口里添加某些东西.因此,我们目前面临的困境是一方面需要向基础类添加方法,另一方面又不能改动基础类.怎样解决这个问题呢? "访问器

与Brian Goetz聊Java的模式匹配

动机 之所有要研究是否有可能在Java中加入模式匹配,主要还是为了改进Java的语言特性.假如有这样的一段代码: if (obj instanceof Integer) { int intValue = ((Integer) obj).intValue(); // 使用intValue } 这段代码做了三个操作: 判断obj是否是一个Integer类型 将obj转成Integer类型 从Integer中抽取出int 现在再来看看在if...else结构的语句中判断其他类型. String for

Java设计模式--访问者模式

访问者模式(别名:虚拟构造) 表示一个作用于某对象结构中的各个元素的操作.它可以在不改变各个元素的类的前提下定义作用于这些元素的新操作. Visitor Pattern Represent an operation to be preformed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements o

设计模式之禅之设计模式-访问者模式

一:访问者模式定义        --->封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作. 二:访问者模式角色● Visitor--抽象访问者        抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的.● ConcreteVisitor--具体访问者        它影响访问者访问到一个类后该怎么干,要做什么事情.● Element--抽象元素        接口或者抽象类,声

设计模式之访问者模式

        刚刚学完设计模式的访问者模式(编译器模式),这里就对该模式进行了总结与分析. 一.产生原因         这里存在一个这样的问题:如果某系统已经完成了一个类层次并提供了满足需求的所有接口,现在要增加新的需求,我们需要怎么做?         可能你会采用增加该需求并把整个层次结构统统修改一遍.然而如果需求变动会不停的发生,而且需求的任何变动都会让整个结构统统修改一遍,此时你会怎么做呢?        所以,我们现在需要对这个系统结构进行重构,访问者模式也许就是你解决上面问题最好

设计模式(C#)系列文章索引

介绍 类图加实例的方式介绍设计模式(C#) 创建型模式(Creational Pattern) 1.设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern) 介绍 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. 2.设计模式(C#) - 建造者模式(Builder Pattern) 介绍 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 3.设计模式(C#) - 原型模式(Prototype Pattern) 介绍 用