返回信息流访问器,字面上的意思是:一个对象,可以用来访问很多种对象。
但是,他的真正目的是:在数据类型固定的情况下,可以扩展操作的数目
比如,定义猫和狗两个对象:
abstract class Animal {
public abstract void speak();
}
class Cat extends Animal {
@Override public void speak() { System.out.println("meow..."); }
}
class Dog extends Animal {
@Override public void speak() { System.out.println("woof!"); }
}
接下来,你可以做两件事:
第一件是固定操作数目,比如只有speak,而扩展数据类型。比如再加一个“牛”。
class Cow extends Animal {
@Override public void speak() { System.out.println("moo"); }
}
这样的扩展不需要visitor模式。面向对象编程的“继承”本身就很适合这样扩展。
但是,如果要固定数据类型不变,只有猫和狗,而要增加操作。那就麻烦了。需要改抽象的接口,还要修改每个具体类。到处都要改。
abstract class Animal {
public abstract void speak();
public abstract void eat();
}
class Cat extends Animal {
@Override public void speak() { System.out.println("meow..."); }
@Override public void eat() { System.out.println("A cat is eating."); }
}
class Dog extends Animal {
@Override public void speak() { System.out.println("woof!"); }
@Override public void eat() { System.out.println("A dog is eating."); }
}
所以,更明智的是使用运行时类型信息(RTTI)。对于Java程序员,可以这样做:
abstract class Animal {
}
class Cat extends Animal {
}
class Dog extends Animal {
}
class AnimalActions {
public static void speak(Animal a) {
if (a instanceof Cat) {
System.out.println("meow...");
} else if (a instanceof Dog) {
System.out.println("woof!");
}
}
}
这样,要再添加操作,只要添加函数就可以了。
class MoreAnimalActions {
public static void eat(Animal a) {
if (a instanceof Cat) {
System.out.println("A cat is eating.");
} else if (a instanceof Dog) {
System.out.println("A dog is eating.");
}
}
}
现在的C++有typeinfo头文件,但早期C++并没有“运行时类型信息”。在那个时候,程序员想实现这样的扩展方式,就必须完全依赖面向对象编程的多态来模拟这种扩展。
// 首先,所有的“访问器”实现同一个接口
interface AnimalVisitor {
void visitCat(Cat cat); // 对于每个可以访问的类型,有一个方法,
void visitDog(Dog dog); // 这个方法传入这个具体对象。
}
// 然后,还要让Animal提供“accept”方法。
abstract class Animal {
// 这个accept的用途是选择Visitor中合适的方法
abstract public void accept(AnimalVisitor v);
}
class Cat {
// 在具体的类中,accept调用visitXxx方法,Xxx是当前类的名字。
@Override public void accept(AnimalVisitor v) { v.visitCat(this); }
}
class Dog{
// 你应该已经发现了:visitor模式的本质就是将“提供‘运行时类型信息’”的责任
// 交给具体的对象本身。选择visitDog而不是visitCat,就等于告诉visitor说,自己
// 的类型是Dog。
@Override public void accept(AnimalVisitor v) { v.visitDog(this); }
}
// 对于每个操作,要定义一个AnimalVisitor类的子类
class Speak implements AnimalVisitor {
// 每个方法处理一个类
@Override public void visitCat(Cat cat) { System.out.println("meow..."); }
@Override public void visitDog(Dog dog) { System.out.println("woof!"); }
}
// 如果想扩展的话,定义新的类就可以了
class Eat implements AnimalVisitor {
@Override public void visitCat(Cat cat) { System.out.println("A cat is eating..."); }
@Override public void visitDog(Dog dog) { System.out.println("A dog is eating..."); }
}
// 使用的方法比较特别
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
// 为了表现出通用性,我们故意定义一个Animal型数组,忽略具体类型。
Animal[] animals = new Animal[] {cat, dog};
Speak speak = new Speak();
Eat eat = new Eat();
// 做动作的方式,是让对象去accept那个visitor。
// 下面这个循环让每个动物speak一次。
for (Animal a: animals) {
a.accept(speak);
}
// 下面让每个动物eat一次。
for (Animal a: animals) {
a.accept(dog);
}
}
}
感觉怎么样?我的感觉是烦。
Scala语言处理这种情况就简单得多。Scala的case match语句很容易实现visitor的思想。case match也是函数式编程的一个基本结构,比if还要基本。
abstract class Animal
case class Cat() extends Animal
case class Dog() extends Animal
def speak(a: Animal): Unit = a match {
case Cat() => println("mew...")
case Dog() => println("woof!")
}
// 只要不断定义函数就可以扩展了。
def eat(a: Animal): Unit = a match {
case Cat() => println("A cat is eating...")
case Dog() => println("A dog is eating...")
}
object Main extends App {
val cat = Cat()
val dog = Dog()
val animals: Seq[Animal] = Seq(cat, dog)
for (a <- animals) speak(a)
for (a <- animals) eat(a)
}
可以说,visitor模式体现了某些编程语言的缺陷:不具备运行时类型信息,以及不具备模式匹配语法。
这是一条镜像帖。来源:北邮人论坛 / java / #37009同步于 2014/12/12
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖
[黑设计模式系列]02:Visitor:访问器模式
nuanyangyang
2014/12/12镜像同步19 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
bd
【 在 nuanyangyang (暖羊羊) 的大作中提到: 】
: 访问器,字面上的意思是:一个对象,可以用来访问很多种对象。
: 但是,他的真正目的是:在数据类型固定的情况下,可以扩展操作的数目
: 比如,定义猫和狗两个对象:
: ...................
等暖神出gitbook...
【 在 yanboyuan (东林的石头) 的大作中提到: 】
: 难道是想来一个系列吗?希望最终能有一个合集啊~
: 通过『我邮2.0』发布