具体元素的接口与实现类
public interface Person {
void accept(Visitor visitor);
}
public class Woman implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Man implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
访问者接口与实现类,分别代表男人与女人在不同的状态下的表现
public interface Visitor {
public void visit(Man man);
public void visit(Woman girl);
}
//成功时Man与Woman的不同表现
public class Success implements Visitor{
public void visit(Man man) {
System.out.println("当男人成功时,背后多半有一个伟大的女人");
}
public void visit(Woman woman) {
System.out.println("当女人成功时,背后大多有一个不成功的男人");
}
}
//恋爱时Man与Woman的不同表现
public class Love implements Visitor{
public void visit(Man man) {
System.out.println("当男人恋爱时,凡事不懂也装懂");
}
public void visit(Woman girl) {
System.out.println("当女人恋爱时,遇事懂也装不懂");
}
}
ObjectStructure与客户端测试代码
import java.util.*;
public class ObjectStructure {
private List<Person> elements = new ArrayList<Person>();
public void attach(Person element){
elements.add(element);
}
public void detach(Person element){
elements.remove(elements);
}
//遍历各种具体元素并执行他们的accept方法
public void display(Visitor visitor){
for(Person p:elements){
p.accept(visitor);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依赖于ObjectStructure
//实例化具体元素
o.attach(new Man());
o.attach(new Woman());
//当成功时不同元素的不同反映
Visitor success = new Success(); //依赖于抽象的Visitor接口
o.display(success);
//当恋爱时的不同反映
Visitor amativeness = new Love(); //依赖于抽象的Visitor接口
o.display(amativeness);
}
}
需求的变化
假设现在需求要扩展数据结构,增加一种具体元素,男与女之外的一种不明物体,我们暂时把它称为“怪兽”,在既有访问者模式的架构下,应该怎样?首先增加一个Bruce类,实现Person接口。最麻烦的是要修改访问者接口及其所有具体访问者!
因为Visit方法中没有包含访问Bruce对象的行为,因此我们被迫要去手工更改Visitor(包括抽象的,具体的),在其中添加有关Bruce对象的行为,这严重违反了“开放-封闭”原则。究其原因在于目前的结构下,被访问对象与访问对象互相依赖,自然不利于分离变化,必须去掉一层依赖关系。
我们尝试把Visitor对Person(元素)的依赖关系去掉,抽象出对应每个具体元素的ElementVisitor接口 -->ManVisitor,WomanVisitor,然后把Visitor对Person的依赖关系转移到ManVisitor与 WomanVisitor身上。
现在Visitor接口已经没有任何抽象方法,只是一个空接口,每一个具体元素对应有一个ElementVisitor接口,每一个元素对应的ElementVisitor接口有访问该元素的visit(),相当把原来在Visitor接口中声明工作,交由各个具体ElementVisitor接口完成。
经过改造后的代码:
原Visitor接口
public interface Visitor {
//退化到没有任何抽象方法
}
新增加ManVisitor,WomanVisitor接口
public interface ManVisitor {
public void visit(Man man);
}
public interface WomanVisitor {
public void visit(Woman w);
}
具体Visitor实现类现在同时实现3个接口
//由实现Visitor接口扩展成实现Visitor,WomanVisitor,ManVisitor三个接口
public class Success implements Visitor,WomanVisitor,ManVisitor{
public void visit(Man man) {
System.out.println("当男人成功时,背后多半有一个伟大的女人");
}
public void visit(Woman girl) {
System.out.println("当女人成功时,背后大多有一个不成功的男人");
}
}
//由实现Visitor接口扩展成实现Visitor,WomanVisitor,ManVisitor三个接口
public class Love implements Visitor,WomanVisitor,ManVisitor{
public void visit(Man man) {
System.out.println("当男人恋爱时,凡事不懂也装懂");
}
public void visit(Woman girl) {
System.out.println("当女人恋爱时,遇事懂也装不懂");
}
}
Person接口没有变化,依旧只依赖于Visitor接口
public interface Person {
void accept(Visitor visitor);
}
改造后的具体元素类Man与Woman
public class Man implements Person {
// 先对visitor进行类型转换,再执行visit方法,因为Visitor接口已经没有声明任何抽象方法了
public void accept(Visitor visitor) {
if (visitor instanceof ManVisitor) {
ManVisitor mv = (ManVisitor) visitor;
mv.visit(this);
}
}
}
public class Woman implements Person {
// 先对visitor进行类型转换,再执行visit方法,因为Visitor接口已经没有声明任何抽象方法了
public void accept(Visitor visitor) {
if (visitor instanceof WomanVisitor) {
WomanVisitor wv = (WomanVisitor) visitor;
wv.visit(this);
}
}
}
ObjectStructure与客户端测试代码没有变化
import java.util.*;
public class ObjectStructure {
private List<Person> elements = new ArrayList<Person>();
public void attach(Person element){
elements.add(element);
}
public void detach(Person element){
elements.remove(elements);
}
//遍历各种具体元素并执行他们的accept方法
public void display(Visitor visitor){
for(Person p:elements){
p.accept(visitor);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依赖于ObjectStructure
//实例化具体元素
o.attach(new Man());
o.attach(new Woman());
//当成功时不同元素的不同反映
Visitor success = new Success(); //依赖于抽象的Visitor接口
o.display(success);
//当恋爱时的不同反映
Visitor amativeness = new Love(); //依赖于抽象的Visitor接口
o.display(amativeness);
}
}
至此改造完毕!我们执行客户端测试代码,结果显示:
当男人成功时,背后多半有一个伟大的女人
当女人成功时,背后大多有一个不成功的男人
当男人恋爱时,凡事不懂也装懂
当女人恋爱时,遇事懂也装不懂
此时,客户端仍然只依赖于Visitor空接口与ObjectStructure类。可能一开始大家会认为空接口没有什么用,现在就能体现出他的威力了,使客户端与具体Visitor的高度解耦!也正是这种思维的核心在JavaAPI中也有类似的应用,这种空接口被称为标识接口。比如java.io.Serializable与java.rmi.Remote等,标识接口里没有任何方法和属性,标识不对实现接口不对实现它的类有任何语义上的要求,它仅仅是表明实现它的类属于一种特定的类型。
上面具体访问者实现的多个接口被称为混合类型。这个概念《Java与模式》中有提及过:当一个具体类处于一个类的等级结构之中的时候,为这个具体类定义一个混合类型是可以保证基于这个类型的可插入性的关键。
=================================无敌分界线====================================
讲了这么长,现在我们测试下改造后的访问者模式
首先增加一种行为(状态),即原访问者模式的优点
增加一个具体访问者Fail,修改一下客户端测试代码
public class Fail implements Visitor,ManVisitor,WomanVisitor{
public void visit(Man man) {
System.out.println("当男人失败时,闷头喝酒,谁也不用劝");
}
public void visit(Woman woman) {
System.out.println("当女人失败时,眼泪汪汪,谁也劝不了");
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依赖于ObjectStructure
//实例化具体元素
o.attach(new Man());
o.attach(new Woman());
//当成功时不同元素的不同反映
Visitor success = new Success(); //依赖于抽象的Visitor接口
o.display(success);
System.out.println();
//当恋爱时的不同反映
Visitor amativeness = new Love(); //依赖于抽象的Visitor接口
o.display(amativeness);
System.out.println();
//新增加失败时的不同反映
Visitor fail = new Fail();
o.display(fail);
}
}
结果显示:
当男人成功时,背后多半有一个伟大的女人
当女人成功时,背后大多有一个不成功的男人
当男人恋爱时,凡事不懂也装懂
当女人恋爱时,遇事懂也装不懂
当男人失败时,闷头喝酒,谁也不用劝
当女人失败时,眼泪汪汪,谁也劝不了
增加新的行为(状态)与原来一样方便!只需要增加一个具体访问者即可!
现在我们来增加一个具体元素(正是写这篇文章的初衷)
首先增加一个具体元素Bruce
public class Bruce implements Person{
public void accept(Visitor visitor) {
if(visitor instanceof BruceVisitor){
BruceVisitor bv = (BruceVisitor) visitor;
bv.visit(this);
}
//这个else可写可不写
else{
String s = visitor.getClass().getName();
String state = s.substring(s.lastIndexOf(".")+1,s.length());
System.out.println("噢..原来怪兽在"+state+"的时候是没有行为的!!");
}
}
}
//按照新的思维方式增加一个对应的ElementVisitor接口
public interface BruceVisitor {
public void visit(Bruce bruce);
}
我们让Success这个具体访问者多实现一个BruceVisitor访问者接口,和修改一下客户端代码进行测试
public class Success implements Visitor,WomanVisitor,ManVisitor,BruceVisitor{
public void visit(Man man) {
System.out.println("当男人成功时,背后多半有一个伟大的女人");
}
public void visit(Woman girl) {
System.out.println("当女人成功时,背后大多有一个不成功的男人");
}
public void visit(Bruce bruce) {
System.out.println("当怪兽成功时.........无语..........");
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依赖于ObjectStructure
o.attach(new Man());
o.attach(new Woman());
o.attach(new Bruce()); //新增一种具体元素Bruce
Visitor success = new Success(); //依赖于抽象的Visitor接口
o.display(success);
System.out.println();
Visitor amativeness = new Love(); //依赖于抽象的Visitor接口
o.display(amativeness);
System.out.println();
Visitor fail = new Fail();
o.display(fail);
}
}
显示结果:
当男人成功时,背后多半有一个伟大的女人
当女人成功时,背后大多有一个不成功的男人
当怪兽成功时.........无语..........
当男人恋爱时,凡事不懂也装懂
当女人恋爱时,遇事懂也装不懂
噢..原来怪兽在Love的时候是没有行为的!!
当男人失败时,闷头喝酒,谁也不用劝
当女人失败时,眼泪汪汪,谁也劝不了
噢..原来怪兽在Fail的时候是没有行为的!!
这个结果你满意吗?
虽然,这只是部分符合“开放-封闭”原则,我们不需要修改Visitor接口,但还是得去修改Success实现新的接口。但是修改具体类比修改接口的代价小得多,不需要重新编译所有访问接口和具体访问者。使我们面对新的变化也容易得多。而且这还有一个好处,就是可以让各种元素有选择地让别人访问,如上述例子,这样使访问者模式的运用起来更加灵活。