在好久之前看过一篇文章,关于如何改造访问者模式,有点感触。在论坛上比较少关于访问者模式的帖子,我把自己的想法写下来,一来与大家分享一下,二来抛砖引玉,希望大家能提出关于改造访问者模式的更好的意见:)
原文在 http://rjx2008.iteye.com/blog/340083写在blog没什么人看,没办法和更多的人一起讨论。
这里非结合反射(Reflection)去解决问题,而是采用另外一种的思维方式去改造访问者模式,相对于反射,我更偏向于这种。用反射改造访问者模式将在 http://rjx2008.iteye.com/blog/345272和 http://rjx2008.iteye.com/blog/345369中有提及过。
这里引用《大话设计模式》中男人与女人原例子。
既定访问者模式的代码:
具体元素的接口与实现类
访问者接口与实现类,分别代表男人与女人在不同的状态下的表现
ObjectStructure与客户端测试代码
需求的变化
假设现在需求要扩展数据结构,增加一种具体元素,男与女之外的一种不明物体,我们暂时把它称为“怪兽”,在既有访问者模式的架构下,应该怎样?首先增加一个Bruce类,实现Person接口。最麻烦的是要 修改访问者接口及其所有具体访问者!
既定访问者模式的类图:
因为Visit方法中没有包含访问Bruce对象的行为,因此我们被迫要去手工更改Visitor(包括抽象的,具体的),在其中添加有关Bruce对象的行为,这严重违反了“开放-封闭”原则。究其原因在于目前的结构下,被访问对象与访问对象互相依赖,自然不利于分离变化,必须去掉一层依赖关系。
我们尝试把Visitor对Person(元素)的依赖关系去掉,抽象出对应每个具体元素的ElementVisitor接口-->ManVisitor,WomanVisitor,然后把Visitor对Person的依赖关系转移到ManVisitor与WomanVisitor身上。
改造后访问者模式的类图:
现在Visitor接口已经没有任何抽象方法, 只是一个空接口,每一个具体元素对应有一个 ElementVisitor接口,每一个元素对应的ElementVisitor接口有访问该元素的visit(),相当把原来在Visitor接口中声明工作,交由各个具体ElementVisitor接口完成。
经过改造后的代码:
原Visitor接口
新增加ManVisitor,WomanVisitor接口
具体Visitor实现类现在同时实现3个接口
Person接口没有变化,依旧只依赖于Visitor接口
改造后的具体元素类Man与Woman
ObjectStructure与客户端测试代码没有变化
至此改造完毕!我们执行客户端测试代码,结果显示:
当男人成功时,背后多半有一个伟大的女人
当女人成功时,背后大多有一个不成功的男人
当男人恋爱时,凡事不懂也装懂
当女人恋爱时,遇事懂也装不懂
此时, 客户端仍然只依赖于Visitor空接口与ObjectStructure类。可能一开始大家会认为空接口没有什么用,现在就能体现出他的威力了,使 客户端与具体Visitor的高度解耦!也正是这种思维的核心
在Java API中也有类似的应用,这种空接口被称为 标识接口。比如java.io.Serializable与java.rmi.Remote等,标识接口里没有任何方法和属性,标识不对实现接口不对实现它的类有任何语义上的要求,它仅仅是表明实现它的类属于一种特定的类型。
上面具体访问者实现的多个接口被称为 混合类型。这个概念《Java与模式》中有提及过:当一个具体类处于一个类的等级结构之中的时候,为这个具体类定义一个混合类型是可以保证基于这个类型的可插入性的关键。
=================================无敌分界线====================================
讲了这么长,现在我们测试下改造后的访问者模式
首先增加一种行为(状态),即原访问者模式的优点
增加一个具体访问者Fail,修改一下客户端测试代码
结果显示:
当男人成功时,背后多半有一个伟大的女人
当女人成功时,背后大多有一个不成功的男人
当男人恋爱时,凡事不懂也装懂
当女人恋爱时,遇事懂也装不懂
当男人失败时,闷头喝酒,谁也不用劝
当女人失败时,眼泪汪汪,谁也劝不了
增加新的行为(状态)与原来一样方便!只需要增加一个具体访问者即可!
现在我们来增加一个具体元素(正是写这篇文章的初衷)
首先增加一个具体元素Bruce
我们让Success这个具体访问者多实现一个BruceVisitor访问者接口,和修改一下客户端代码进行测试
显示结果:
当男人成功时,背后多半有一个伟大的女人
当女人成功时,背后大多有一个不成功的男人
当怪兽成功时.........无语..........
当男人恋爱时,凡事不懂也装懂
当女人恋爱时,遇事懂也装不懂
噢..原来怪兽在Love的时候是没有行为的!!
当男人失败时,闷头喝酒,谁也不用劝
当女人失败时,眼泪汪汪,谁也劝不了
噢..原来怪兽在Fail的时候是没有行为的!!
这个结果你满意吗?
虽然,这只是 部分符合“开放-封闭”原则,我们不需要修改Visitor接口,但还是得去修改Success实现新的接口。但是 修改具体类比修改接口的代价小得多,不需要 重新编译所有访问接口和具体访问者。使我们面对新的变化也容易得多。而且这还有一个好处,就是可以让各种元素 有选择地让别人访问,如上述例子,这样使访问者模式的运用起来 更加灵活。
原文在 http://rjx2008.iteye.com/blog/340083写在blog没什么人看,没办法和更多的人一起讨论。
这里非结合反射(Reflection)去解决问题,而是采用另外一种的思维方式去改造访问者模式,相对于反射,我更偏向于这种。用反射改造访问者模式将在 http://rjx2008.iteye.com/blog/345272和 http://rjx2008.iteye.com/blog/345369中有提及过。
这里引用《大话设计模式》中男人与女人原例子。
既定访问者模式的代码:
具体元素的接口与实现类
- 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接口。最麻烦的是要 修改访问者接口及其所有具体访问者!
既定访问者模式的类图:
![](http://www.iteye.com/upload/attachment/82055/5414f811-c906-3c5d-87ad-5e4a6b898bcc.jpg)
因为Visit方法中没有包含访问Bruce对象的行为,因此我们被迫要去手工更改Visitor(包括抽象的,具体的),在其中添加有关Bruce对象的行为,这严重违反了“开放-封闭”原则。究其原因在于目前的结构下,被访问对象与访问对象互相依赖,自然不利于分离变化,必须去掉一层依赖关系。
我们尝试把Visitor对Person(元素)的依赖关系去掉,抽象出对应每个具体元素的ElementVisitor接口-->ManVisitor,WomanVisitor,然后把Visitor对Person的依赖关系转移到ManVisitor与WomanVisitor身上。
改造后访问者模式的类图:
![](http://www.iteye.com/upload/attachment/82057/7921b788-0864-3892-9ae9-1f6783f86e84.jpg)
现在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的高度解耦!也正是这种思维的核心
在Java API中也有类似的应用,这种空接口被称为 标识接口。比如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的时候是没有行为的!!
这个结果你满意吗?
![](https://i-blog.csdnimg.cn/blog_migrate/fdae2ee6389663d0e8e8cedf36783999.gif)
虽然,这只是 部分符合“开放-封闭”原则,我们不需要修改Visitor接口,但还是得去修改Success实现新的接口。但是 修改具体类比修改接口的代价小得多,不需要 重新编译所有访问接口和具体访问者。使我们面对新的变化也容易得多。而且这还有一个好处,就是可以让各种元素 有选择地让别人访问,如上述例子,这样使访问者模式的运用起来 更加灵活。