目录

一、什么是访问者模式

二、访问者模式的结构

三、访问者模式的适用性

四、 场景举例

五、访问者模式的特点


一、什么是访问者模式

访问者(Visitor)模式是一种对象的行为模式。在访问者模式里,每个访问者表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

在面向对象的系统开发和设计过程中,经常遇到的一种情况就是需求变更,针对已经开发完成的系统,客户又会提出新的需求。因此,我们不得不去修改已有的设计,最常见就是解决方案就是给已经设计、实现好的类添加新的方法去实现客户新的需求,这样就陷入了设计变更的梦魇:不停地打补丁,其最大的问题就是设计根本就不可能封闭。

访问者模式则为这种情况提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。

二、访问者模式的结构

访问者模式涉及的角色及其职责如下:

抽象访问者(Visitor)角色:为该对象结构中具体元素角色声明一个访问操作接口,用来代表为对象结构添加的新功能,理论上可以代表任意的功能。

具体访问者(ConcreteVisitor)角色:实现每个由抽象访问者角色(Visitor)声明的操作接口。

抽象元素(Element)角色:定义一个accept()操作,它以一个访问者(Visitor)对象作为参数。

具体元素(ConcreteElement)角色:实现由抽象元素(Element)角色提供的accept()操作。

对象结构(ObjectStructure)角色:这是使用访问者模式必备的角色。一般具备以下特征:

1)能枚举它的元素。

2)可以提供一个高层的接口以允许该访问者访问它的元素。

3)可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。

访问者模式结构示意源代码如下:

(1)首先定义一个接口来代表要新加入的功能,把它称作访问者(Visitor),访问谁呢?当然是访问对象结构中的对象了。

/** * 访问者接口 */
public interface Visitor {  /** * 访问ConcreteElementA,相当于为ConcreteElementA添加的新功能 */  public void visitConcreteElementA(ConcreteElementA elementA);  /** * 访问ConcreteElementB,相当于为ConcreteElementB添加的新功能 */  public void visitConcreteElementB(ConcreteElementB elementB);  }  

(2)再看看访问者的具体实现(ConcreteVisitor)。

public class ConcreteVisitorA implements Visitor {  @Override  public void visitConcreteElementA(ConcreteElementA elementA) {  /** * 把访问ConcreteElementA时,需要执行的功能在这里进行实现 可能需要访问元素已有的功能,比如:operationA() */  System.out.println("ConcreteVisitorA 访问 ==> ConcreteElementA 对象。");  }  @Override  public void visitConcreteElementB(ConcreteElementB elementB) {  /** * 把访问ConcreteElementB时,需要执行的功能在这里进行实现 可能需要访问元素已有的功能,比如:operationB() */  System.out.println("ConcreteVisitorA 访问 ==> ConcreteElementB 对象。");  }  }  
public class ConcreteVisitorB implements Visitor {  @Override  public void visitConcreteElementA(ConcreteElementA elementA) {  /** * 把访问ConcreteElementA时,需要执行的功能在这里进行实现 可能需要访问元素已有的功能,比如:operationA() */  System.out.println("ConcreteVisitorB 访问 ==> ConcreteElementA 对象。");  }  @Override  public void visitConcreteElementB(ConcreteElementB elementB) {  /** * 把访问ConcreteElementB时,需要执行的功能在这里进行实现 可能需要访问元素已有的功能,比如:operationB() */  System.out.println("ConcreteVisitorB 访问 ==> ConcreteElementB 对象。");  }  }  

(3)抽象元素(Element)的定义。

public abstract class Element {  // 接受访问者的访问  public abstract void accept(Visitor visitor);  }  

(4)再看看元素对象的具体实现(ConcreteElement)。

public class ConcreteElementA extends Element {  @Override  public void accept(Visitor visitor) {  // 回调访问者对象的相应方法  visitor.visitConcreteElementA(this);  }  /** * 示例方法,表示元素已有的功能实现 */  public void operationA() {  System.out.println("执行ConcreteElementA已有的operationA方法.");  }
}  
​
public class ConcreteElementB extends Element {  @Override  public void accept(Visitor visitor) {  // 回调访问者对象的相应方法  visitor.visitConcreteElementB(this);  }  /** * 示例方法,表示元素已有的功能实现 */  public void operationB() {  System.out.println("执行ConcreteElementB已有的operationB方法.");  }
}  ​

(5)对象结构(ObjectStructure)示例代码如下。

import java.util.ArrayList;
import java.util.Collection;  /** * 对象结构,通常在这里对元素对象进行遍历,让访问者能够访问到所有的元素 */
public class ObjectStructure {  /** * 示意,表示对象结构,可以是一个组合结构或者集合 */  private Collection<Element> col = new ArrayList<Element>();  /** * 示意方法,提供给客户端操作的高层接口,让访问者对对象结构中的所有元素进行访问 */  public void handleRequest(Visitor visitor) {  // 循环对象结构中的元素,进行访问  for (Element element : col) {  element.accept(visitor);  }  }  /** * 示意方法,组建对象结构,向对象结构中添加元素 */  public void addElement(Element element) {  this.col.add(element);  }
}  

(6)最后在客户端测试一下,示例代码如下。

public class Client {  public static void main(String[] args) {  // 创建对象结构  ObjectStructure os = new ObjectStructure();  // 为对象结构中添加元素对象  os.addElement(new ConcreteElementA());  os.addElement(new ConcreteElementB());  // 创建访问者  Visitor visitor = new ConcreteVisitorA();  // 调用对象结构的业务处理方法  os.handleRequest(visitor);  }  }  

运行程序打印结果如下:

ConcreteVisitorA 访问 ==> ConcreteElementA 对象。
ConcreteVisitorA 访问 ==> ConcreteElementB 对象。 

访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性。

访问者模式在不破坏类的前提下,为类提供增加新的新操作,其实现的关键是双分派( Double-Dispatch)的技术。 在访问者模式中 accept()操作是一个双分派的操作。具体调用哪一个具体的accept()操作,有两个决定因素:

1)Element的类型。因为 accept()是多态的操作,需要具体的 Element 类型的子类才可以决定到底调用哪一个accept()实现;

2)Visitor的类型。accept()操作有一个参数( Visitor visitor),要决定了实际传进来的 Visitor 的实际类别才可以决定具体是调用哪个 VisitConcrete()实现。

三、访问者模式的适用性

从访问者模式的定义可以看出对象结构是使用访问者模式的先决条件,在以下情况可以考虑使用访问者模式:

(1) 对象结构内包含多种类型的对象,我们希望对这些对象实施一些依赖于其具体类型的操作。

(2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

(3) 对象结构中对象的类型很少改变,但经常需要在此对象结构上定义新的操作。

四、 场景举例

一个商场(SuperMarket),通常都会包括(当然还会包含一些其他的组成部分):商店(Store)、监控室(MonitoringRoom)、卫生间(WaterCloset)。商场的访问者大致可以分为两大类:顾客(Customer)、商场工作人员(MarketStaff)。顾客可以逛商店、上卫生间,但却不能进入监控室;工作人员可以进入监控室、上卫生间,但却不能像顾客一样逛商店(除非他不想干了),也就是说对于商场的同一个地点,不同的访问者有不同的行为权限,而且访问者的种类很有可能需要根据时间的推移发生变化(没准哪天,工商局的人要来视察呢!此时就需要增加工商局人员的访问者了。)。

使用访问者模式实现,其类图如下:

Place相当于访问者模式中的Element。

public abstract class Place {// 接受访问者的访问public abstract void accept(Visitor visitor);
}

Place的具体实现类有三个,WaterCloset、Store和MonitoringRoom。

public class WaterCloset extends Place {@Overridepublic void accept(Visitor visitor) {// 回调访问者对象的相应方法visitor.visitWaterCloset(this);}public void washing() {// 卫生间已有的方法System.out.println("洗洗手。。。*/");}
}
public class Store extends Place {@Overridepublic void accept(Visitor visitor) {// 回调访问者对象的相应方法visitor.visitStore(this);}public void shopping() {// 商店已有的方法System.out.println("欢迎光临,祝您购物愉快。。。*/");}
}
public class MonitoringRoom extends Place {@Overridepublic void accept(Visitor visitor) {// 回调访问者对象的相应方法visitor.visitMonitoringRoom(this);}public void watching() {// 监控室已有的方法System.out.println("查看监控录像...*/");}}

抽象访问者Visitor的代码如下。

public interface Visitor {// 进入卫生间public void visitWaterCloset(WaterCloset wc);// 进入监控室public void visitMonitoringRoom(MonitoringRoom mr);// 进入商店public void visitStore(Store store);}

Visitor的具体实现类,MarketStaff和Customer。

public class MarketStaff implements Visitor {@Overridepublic void visitWaterCloset(WaterCloset wc) {System.out.println("/*工作人员来到卫生间。。。");wc.washing();System.out.println();}@Overridepublic void visitMonitoringRoom(MonitoringRoom mr) {System.out.println("/*工作人员来到监控室。。。");mr.watching();System.out.println();}@Overridepublic void visitStore(Store store) {System.out.println("/*工作人员来到商店。。。");System.out.println("现在是工作时间,请专心工作。。。*/");System.out.println();}}
public class Customer implements Visitor {@Overridepublic void visitWaterCloset(WaterCloset wc) {System.out.println("/*顾客来到卫生间。。。");wc.washing();System.out.println();}@Overridepublic void visitMonitoringRoom(MonitoringRoom mr) {System.out.println("/*顾客来到监控室。。。");System.out.println("非工作人员禁止入内。。。*/");System.out.println();}@Overridepublic void visitStore(Store store) {System.out.println("/*顾客来到商店。。。");store.shopping();System.out.println();}}

SuperMarket就是结构对象。

import java.util.ArrayList;
import java.util.Collection;public class SuperMarket {// 示意,表示对象结构,可以是一个组合结构或者集合private Collection<Place> col = new ArrayList<Place>();public void handleRequest(Visitor visitor) {// 循环对象结构中的元素,进行访问for (Place place : col) {place.accept(visitor);}}// 示意方法,组建对象结构,向对象结构中添加元素public void addPlace(Place place) {this.col.add(place);}
}

Client中测试代码如下。

public class Client {public static void main(String[] args) {SuperMarket superMarket = new SuperMarket();superMarket.addPlace(new WaterCloset());superMarket.addPlace(new Store());superMarket.addPlace(new MonitoringRoom());// 创建一个顾客访问者Visitor customer = new Customer();System.out.println("=====顾客来到商场=====");superMarket.handleRequest(customer);System.out.println("=====顾客离开商场=====");System.out.println();// 创建一个商场工作人员访问者Visitor visitor = new MarketStaff();System.out.println("=====工作人员来到商场=====");superMarket.handleRequest(visitor);System.out.println("=====工作人员离开商场=====");}}

运行程序打印结果如下:

=====顾客来到商场=====
/*顾客来到卫生间。。。
洗洗手。。。*//*顾客来到商店。。。
欢迎光临,祝您购物愉快。。。*//*顾客来到监控室。。。
非工作人员禁止入内。。。*/=====顾客离开商场==========工作人员来到商场=====
/*工作人员来到卫生间。。。
洗洗手。。。*//*工作人员来到商店。。。
现在是工作时间,请专心工作。。。*//*工作人员来到监控室。。。
查看监控录像...*/=====工作人员离开商场=====

五、访问者模式的特点

访问者模式的优点  

●  好的扩展性   能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。   

●  好的复用性   可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。   

●  分离无关行为   可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

访问者模式的缺点  

●  增加新的元素类很困难   在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。   

●  破坏封装   访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。

JAVA设计模式--访问者模式相关推荐

  1. java设计模式---访问者模式

      Java深入到一定程度,就不可避免的碰到设计模式这一概念,了解设计模式,将使自 己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广 泛,遵循一定的编程模式,才能使自 ...

  2. Java设计模式-访问者模式

    访问者模式   在现实生活中,有些集合对象中存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式.例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同:还有顾客在商场购物时放在&qu ...

  3. Java设计模式—访问者模式

    访问者模式 Visitor Pattern 1. 访问者模式概述 1.1 定义 1.2 结构 1.3 适用环境 2. 访问者模式应用案例 2.1 类图 2.2 代码实现 2.3 运行结果 3. 访问者 ...

  4. 设计模式---访问者模式

    访问者模式 介绍 定义及使用场景 UML类图 角色 财务案例 个人心得体会 静态分派以及动态分派 静态分派 动态分派 访问者模式中的伪动态双分派 对访问者模式的一些思考 总结 优点 缺点 适用性 参考 ...

  5. Java 设计模式——状态模式

    概述 很多人在说状态模式的时候总拿策略模式来进行对比,可能他们的类图会有一点类似,可我却不认为他们有多么相像.你可以阅读<Java设计模式--策略模式>这篇博客,并与本文对比,以找到蛛丝马 ...

  6. Java设计模式——Builder模式

    前言 之前写Android程序的时候,经常会用到Dialog(对话框)这个控件.我们在使用Dialog,比如AlertDialog的时候就用到了这里要说明的Builder模式.现在我们来看一下这样的一 ...

  7. Java设计模式-工厂模式(3)抽象工厂模式

    在Java设计模式-工厂模式(2)工厂方法模式 我们知道了工厂方法模式解决了简单工厂模式中的缺陷,做到了满足开闭原则,但是时代是进步的,进而又产生新的问题,工厂难道只能生产一种东西吗.我们所见到的工厂 ...

  8. Java设计模式-工厂模式(2)工厂方法模式

    在Java设计模式-工厂模式(1)简单工厂模式 中我们介绍了简单工厂模式,提到了简单工厂模式违背了开闭原则,而"工厂方法模式"是对简单工厂模式的进一步抽象化,其好处是可以使系统在不 ...

  9. Java设计模式-工厂模式(1)简单工厂模式

    Java设计模式-工厂模式(1)简单工厂模式 一.前言 1)例子 2)类图关系 3)代码实现 二.简单工厂模式 2.1.概述: 2.2.类图关系: 2.3.代码修改: 2.4.优缺点 2.5.扩展-简 ...

最新文章

  1. win8.1升级到win10后 vmware不能连网的问题
  2. Java虚拟机学习(7):对象内存分配与回收
  3. 2021中超1 1010 zoto
  4. boost::allocate_shared_noinit相关的测试程序
  5. AngularJS开发指南14:AngularJS的服务详解
  6. gj5 自定义序列类
  7. Erwin 生成 mysql 带注释(comment )的脚本
  8. 什么是带内管理 带外管理?(转)
  9. 如何在 iOS 15 和 macOS Monterey 的 Safari 中隐藏 IP 地址?
  10. With great power comes great responsibility
  11. type=button 字体大一点_CAD设计师喜欢用SHX字体的原因你知道吗?
  12. MAC 外接键盘卡顿处理
  13. 深信服虚拟服务器设置ip,深信服网关怎么设置端口映射
  14. GetFLV.v9.1.1.8-kg-REPT
  15. gdal切火星偏移的瓦片
  16. Unity 3D VR项目 动物园
  17. 1条命令解决不能完成此操作,因为项目“Karabiner-Elements”已被锁定
  18. 西数硬盘砍头流程说明
  19. 深入理解计算机网络-8网络层5
  20. DM8168 - BT656格式视频采集

热门文章

  1. 你好,放大器——失调电压漂移(Offset Voltage Drift)
  2. ubuntu 挂载 网件路由器 存储设备
  3. SAP PLM模块常用表及表关系
  4. 第五十三周总结——云文档开发一周总结
  5. 删除鼠标右击的Open Folder as PyCharm和其他相同情况快捷键
  6. CIKM 2022 | 基于文本增强和缩略-恢复策略的缩略词Transformer
  7. Java并发编程--学会他就能抵挡千军万马
  8. Ubuntu系统三种安装软件包的方法(apt/aptitude/deb命令)和常用的镜像源
  9. 数据库备份与恢复简介
  10. java多文件上传plupload控件实现多图片上传(二)