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 u

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的对象,也是个不错的选择。

目录
相关文章
|
前端开发 API
patch使用
+ put:对所有资源进行更新 + patch:对部分资源进行更新 put使用方法和post相同,但是put是幂等的。
|
前端开发 Java 开发者
Dispatch 设计| 学习笔记
快速学习 Dispatch 设计。
Dispatch 设计| 学习笔记
|
JSON 数据格式
parse 和parseObject 有什么区别
JSON.parse()返回的结果是Object对象
177 0
|
开发工具 git
生成patch
生成patch
|
API Windows
ASIO的post和dispatch方法
ASIO的post和dispatch方法的实现和差别
1202 0
|
监控
Dispatch Source 应用
Dispatch Source 源是一个偏底层的函数集合,使用时CPU负荷非常小,尽量不占资源,开发过程中大多是配合定时器使用。
161 0
EventBus: Could not dispatch event: class com.********.LoginEvent to subscribing class
Could not dispatch event 04-18 14:10:11.062 4790-4790/com. E/EventBus: Could not dispatch event: class com.
8485 0
|
前端开发
获取this.$store.dispatch的返回值
获取this.$store.dispatch的返回值
1003 0
|
前端开发 Java 开发者
Dispatch设计|学习笔记
快速学习Dispatch设计
Dispatch设计|学习笔记
|
Android开发
一分钟学会使用9-patch
背景 之前项目开发一般都是写逻辑,UI方面的操作用的比较少。 图片也是直接使用设计师提供的。 因此一般情况下不需要用到9-patch图。 然而,最近项目中分配给我的功能用到了。
1448 0