`

访问者模式进阶(二)

阅读更多
       这一篇讲述如何让访问者模式(Visitor)变得更加灵活。但并非结合反射(Reflection)去解决问题,而是采用另外一种的思维方式去改造访问者模式,相对于反射,我更偏向于这种。至于用反射改造访问者模式将会在下一篇讨论。

         用回上一篇文章的例子,男人女人。假设现在需求要扩展数据结构,增加一种具体元素,男与女之外的一种不明物体,我们暂时把它称为“怪兽”,在既有访问者模式的架构下,应该怎样?首先增加一个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的高度解耦!也正是这种思维的核心
在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的时候是没有行为的!!


这个结果你满意吗?
诚然,这并只是部分符合“开放-封闭”原则,我们不需要修改Visitor接口,但还是得去修改Success实现新的接口。但是修改具体类比修改接口的代价小得多,不需要重新编译所有访问接口和具体访问者。使我们面对新的变化也容易得多。而且这还有一个好处,就是可以让各种元素有选择地让别人访问,如上述例子,这样使访问者模式的运用起来更加灵活
  • 大小: 50 KB
  • 大小: 60.2 KB
分享到:
评论
1 楼 sanpic 2015-07-23  
请问你的UML是用什么工具画的,谢谢

相关推荐

    java进阶必看书籍

    包括创建型模式(如单例模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式)、结构型模式(如适配器模式、装饰器模式、代理模式、桥接...访问者模式、责任链模式、命令模式、备忘录模式、状态模式和解释器模式)...

    LUA进阶源代码欣赏

    源代码中可能包含一些LUA特有的设计模式,如单例模式、观察者模式等,通过这些模式,开发者可以编写出更加优雅和可维护的代码。 总的来说,"LUA进阶源代码欣赏"是一个全面了解和提升LUA编程技巧的宝贵资源。通过...

    C++进阶(语法篇)

    设计模式则是面向对象软件工程中用于解决特定问题的一般性模板,它包含了多个经典模式如工厂模式、单例模式、策略模式、观察者模式、适配器模式等,这些模式在C++编程实践中经常被应用。 通过本文对C++进阶语法的...

    进阶篇.pdf

    行为型设计模式如模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式,主要处理对象间的交互和行为。 单例模式有多种实现方式,...

    stm32进阶实验应用

    进阶实验可能会涉及GPIO的模拟I/O、上下拉电阻配置、中断触发模式设置等,以及使用GPIO实现简单的通信协议,如I2C或SPI。 3. **ADC与DAC**:STM32内置了模拟到数字转换器(ADC)和数字到模拟转换器(DAC),使得它...

    Java进阶之设计模式内含源码以及说明书可以自己运行复现.zip

    行为型设计模式,观察者模式定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。在Java中,java.util.Observable和java.util.Observer接口就是实现这一模式...

    java中级进阶高级23种设计模式详细介绍+代码详解PPT模板.pptx

    21. 访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 22. 中介者模式:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要...

    2011.06 - Java语言程序设计-进阶篇(原书第8版

    进阶篇可能会涉及一些Java中常用的模式,例如单例模式、工厂模式、观察者模式等。 10. 开发工具和环境:最后,进阶篇也可能简要介绍Java开发中常用的一些工具和环境,如集成开发环境(IDEs)、版本控制(如Git)...

    C++二十三种设计模式.pdf

    17. **访问者模式**:访问者模式表示一个作用于某对象结构中的各元素的操作,它可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 18. **状态模式**:状态模式允许对象在其内部状态改变时改变其行为,...

    Java设计模式(第二版)中文版

    - **访问者模式**(Visitor Pattern) 4. **设计模式的实际应用** - 本书提供了大量的实际案例,通过这些案例学习如何将设计模式应用于具体的软件项目中。 - 每个设计模式都配有详细的代码实现和应用场景分析,...

    Java设计模式

    标签"DesignPattern2"暗示了这是关于设计模式系列的第二部分,可能涵盖了一些进阶主题,包括但不限于工厂模式、单例模式、建造者模式、原型模式、适配器模式、装饰器模式、桥接模式、代理模式、职责链模式、命令模式...

    重塑java基础高级进阶资源分享

    - **观察者模式**:当对象的状态改变时,所有依赖于它的对象都会得到通知并被自动更新。 #### 5. 高级框架和技术 - **Spring框架**:提供了一种轻量级的DI容器,简化了Java EE应用程序开发。 - **Hibernate**:是一...

    C++二十三种设计模式

    如策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、迭代器模式(Iterator)、命令模式(Command)、备忘录模式(Memento)、状态模式(State)、访问者模式(Visitor)、职责链...

    Java进阶编程(经典网帖合集)

    10. **设计模式**:学习和掌握常见的设计模式,如单例、工厂、观察者、装饰者、策略、代理等,能够提升代码的可复用性和可维护性。 11. **注解(Annotation)**:注解是Java提供的一种元数据,用于提供编译时和运行...

    stm32基础及进阶学习

    这个压缩包文件提供的是关于STM32的基础和进阶学习资料,包括初级篇、中级篇和系统篇,总计3本书,共计334页,旨在帮助学习者全面了解并掌握STM32的使用。 《零死角玩转STM32+初级篇》(131页,4.3M)是入门STM32的...

    KVM实战:原理、进阶与性能调优.pdf

    进阶部分(第二篇)从第6章开始,介绍了KVM设备高级管理。半虚拟化驱动是本章的一个重点,包括virtio驱动、virtio_balloon、virtio_net、virtio_blk、vhost-net后端以及网卡多队列、用户态vhost-user后端驱动、kvm_...

    Istio 服务网格进阶实战.pdf

    总结起来,Istio服务网格进阶实战主要涵盖了Istio的概念原理、服务网格的架构与实现模式、数据平面与控制平面的配置与管理、流量管理基础概念以及安全、监控和多集群部署等多个方面。随着微服务架构在云计算时代的...

    Tableau数据分析可视化高手进阶

    此外,还将学习如何使用LOD(Level of Detail)表达式进行复杂的数据聚合和细分分析,这对于理解和展示大规模数据集的模式至关重要。 在数据可视化方面,课程将教授如何选择合适的图表类型,如条形图、折线图、散点...

    《Java语言程序设计(进阶篇)》 课后习题第24章代码chapter24.rar

    在Java中,如单例模式、工厂模式、观察者模式等可能在习题中出现,旨在提高代码的可维护性和可扩展性。 综上所述,这些知识点构成了Java进阶编程的重要组成部分,通过《Java语言程序设计(进阶篇)》第24章的课后...

Global site tag (gtag.js) - Google Analytics