1. Visitor模式简介

​ Visitor是访问者的意思。

​ 一个数据结构包含多种数据元素,这些数据元素方便存储,却不一定方便使用。因此我们有必要架起一座桥梁,即对数据元素进行处理,方便别人使用。问题来了:数据的处理是放在数据结构类里面还是再造一个类专门用来处理?

​ 如果一个数据结构需要多种处理方式,那每增加一种就要修改数据结构的类,显然不合适。访问者模式就是解决这个问题来的。在这个模式中,数据结构和处理被分离开。我们编写一个访问者,即专门处理数据元素的类来处理数据元素,一旦有新的处理方式,就编写新的访问者类去访问数据结构即可。

2. 示例

接下来的例子以之前的Composite模式为基础编写。为File和Directory类添加一个accept方法,这个方法接收Visitor类,即访问者类。通过这个访问者我们能获得File和Directory类的经过处理的数据展示。

2.1 类图

2.2 代码

ListVisitor里面的visit(Directory directory)使用了递归,值得好好体会一下。

public abstract class Visitor {
public abstract void visit(File file);
public abstract void visit(Directory directory);
} public class ListVisitor extends Visitor {
private String currentdir = "";
public void visit(File file) {
System.out.println(currentdir + "/" + file);
}
public void visit(Directory directory) {
System.out.println(currentdir + "/" + directory);
String savedir = currentdir;
currentdir = currentdir + "/" + directory.getName();
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.accept(this);
}
currentdir = savedir;
}
} public interface Element {
public abstract void accept(Visitor v);
} public abstract class Entry implements Element {
public abstract String getName();
public abstract int getSize();
//添加元素方法,抽象的父类无法添加
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
//便利元素方法,抽象的父类无法遍历
public Iterator iterator() throws FileTreatmentException {
throw new FileTreatmentException();
}
public String toString() { // 显示字符串
return getName() + " (" + getSize() + ")";
}
} public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void accept(Visitor v) {
v.visit(this);
}
} public class Directory extends Entry {
private String name; // 文件夹名字
private ArrayList dir = new ArrayList(); // 目录条目集合 public Directory(String name) { // 构造函数
this.name = name;
}
public String getName() { // 获取名字
return name;
}
public int getSize() { // 获取大小
int size = 0;
Iterator it = dir.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) { // 增加目录条目
dir.add(entry);
return this;
}
public Iterator iterator() { // 生成Iterator
return dir.iterator();
}
public void accept(Visitor v) { // 接受访问者的访问
v.visit(this);
}
} public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.accept(new ListVisitor()); System.out.println("");
System.out.println("Making user entries...");
Directory xiaoming = new Directory("小明");
Directory xiaohong = new Directory("小红");
Directory xiaohua = new Directory("小花");
usrdir.add(xiaoming);
usrdir.add(xiaohong);
usrdir.add(xiaohua);
xiaoming.add(new File("diary.html", 100));
xiaoming.add(new File("Composite.java", 200));
xiaohong.add(new File("memo.tex", 300));
xiaohua.add(new File("game.doc", 400));
xiaohua.add(new File("junk.mail", 500));
rootdir.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
} /////////////////////////////结果////////////////////////////////
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0) Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/小明 (300)
/root/usr/小明/diary.html (100)
/root/usr/小明/Composite.java (200)
/root/usr/小红 (300)
/root/usr/小红/memo.tex (300)
/root/usr/小花 (900)
/root/usr/小花/game.doc (400)
/root/usr/小花/junk.mail (500)

3. 模式的角色和类图

  • Visitor(访问者): 抽象的访问者类,里面声明了供Element调用的访问方法visit。本例中由Visitor类扮演此角色。
  • ConcreteVisitor(具体的访问者):具体的访问者类,里面实现了供Element调用的访问方法visit,本例中由ListVisitor类扮演此角色。
  • Element(元素):表示Visitor访问的对象,声明了接收访问者的accept方法,参数是Visitor角色,本例中由Element接口扮演此角色。
  • ConcreteElement(具体的元素):实现Element定义的接口,本例中由File类和Directory类扮演此角色。
  • ObjectStructure(对象结构):负责处理Element角色的集合。ConcreteVisitor角色为每个Element角色都准备了处理方法,在本例中,由Directory类扮演此角色。为了让ConcreteVisitor可以遍历处理每个Element角色,在示例程序中,我们在Directory类中实现了iterator方法。

4. 思路拓展

4.1 双重分发

在代码中,元素接受访问者:element.accept(visitor),访问者访问元素visitor.visit(element),对比这两个方法,他们是相反的关系,这种消息分发的方式被称为双重分发。

4.2 开闭原则

  • 对扩展开放

  • 对修改关闭

如果要增加功能,要在不修改现有代码的前提下进行扩展。Visitor模式中,如果要扩展数据结构的访问方法,不需要修改数据结构的类, 只需要增加一个访问者类即可。体现了开闭原则。

4.3 难以增加ConcreteElement角色

​ Visitor模式很容易增加ConcreteVisitor角色,却很难应对ConcreteElement角色的增加。假设我们要增加Entry类的子类Device类,这个类和File类、Directory类是兄弟关系。我们就需要在Visitor类中声明一个visit(Device)方法,所有Visitor类的子类都要实现这个方法。

4.4 Visitor工作所需的条件

​ Visitor需要从数据结构中获取到足够多的信息才能够工作。如果数据结构向Visitor公开了不该公开的信息,将来对数据结构的改良就会变得非常困难。

《图解设计模式》读书笔记6-1 VISITOR模式的更多相关文章

  1. HeadFirst设计模式读书笔记(3)-装饰者模式(Decorator Pattern)

    装饰者模式:动态地将责任附件到对象上.若要扩展功能,装饰者提东了比继承更有弹性的替代方案. 装饰者和被装饰对象有相同的超类型 你可以用一个或者多个装饰者包装一个对象. 既然装饰者和被装饰对象有相同的超 ...

  2. HeadFirst设计模式读书笔记--目录

    HeadFirst设计模式读书笔记(1)-策略模式(Strategy Pattern) HeadFirst设计模式读书笔记(2)-观察者模式(Observer Pattern) HeadFirst设计 ...

  3. Head First 设计模式读书笔记(1)-策略模式

    一.策略模式的定义 策略模式定义了算法族,分别封装起来,让它们之间可以互换替换,此模式让算法的变化独立使用算法的客户. 二.使用策略模式的一个例子 2.1引出问题 某公司做了一套模拟鸭子的游戏:该游戏 ...

  4. JavaScript设计模式:读书笔记(未完)

    该篇随我读书的进度持续更新阅读书目:<JavaScript设计模式> 2016/3/30 2016/3/31 2016/4/8 2016/3/30: 模式是一种可复用的解决方案,可用于解决 ...

  5. C#设计模式学习笔记:(21)访问者模式

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8135083.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第九个模式--访 ...

  6. 图解http读书笔记

    以前对HTTP协议一知半解,一直不清楚前端需要对于HTTP了解到什么程度,知道接触的东西多了,对于性能优化.服务端的配合和学习中也渐渐了解到了HTTP基础的重要性,看了一些大神对HTTP书籍的推荐,也 ...

  7. Java设计模式学习笔记(二) 简单工厂模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 正文开始... 1. 简介 简单工厂模式不属于GoF23中设计模式之一,但在软件开发中应用也较为 ...

  8. Java设计模式学习笔记(三) 工厂方法模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 简介 上一篇博客介绍了简单工厂模式,简单工厂模式存在一个很严重的问题: 就是当系统需要引入 ...

  9. Java设计模式学习笔记(四) 抽象工厂模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 抽象工厂模式概述 工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问 ...

  10. C#设计模式学习笔记:(23)解释器模式

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8242238.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第十一个模式-- ...

随机推荐

  1. selenium向IE的输入框中输入字符时特别慢

    selenium向IE的输入框中输入字符时特别慢,需要去selenium官网下载32位的iedriver,替换掉64位的,即可解决.

  2. Ubuntu下的图形化多线程下载器XDM

    目录 1.下载 2.安装 3.浏览器支持 使用Ubuntu下载东西经常过于缓慢,因此需要多进程下载器. 1.下载 下载链接:http://xdman.sourceforge.net/#download ...

  3. word中迅速将表格一分为二 拆分表格快捷键ctrl+shift+enter 重复上一个命令快捷键f4

    这里说的是将一个表格拆分为两个表格 选择要拆分的行,快捷键ctrl+shift+enter,就拆分为两个表格了,是不是很快! 在多个表格需要拆分的时候,做一次这样的操作,然后不停的移动.F4,就可以了 ...

  4. MFC---导出 Excel 方法

    本方法通过Excel驱动写入 请添加头文件 #include"afxdb.h" 第一步创建Excel文件 安装驱动 CString FileName = L"first. ...

  5. BZOJ 1109 (LIS)

    题面 传送门 分析 设dp[i]是第i个积木在自己的位置上时,前i个积木中最多能回到自己位置的数目. \(dp[i]=max(dp[j])+1 (i>j,a[i]>a[j],a[i]-a[ ...

  6. Centos7.6替换自带的jre安装jdk

    Centos7.6自带jre 1.8,可以作为java运行环境.但如果要编译java程序那就需要jdk,以下介绍如何把自带的jre卸掉并安装jdk 首先要卸载自带的jre PS:由于不同版本的操作系统 ...

  7. BZOJ 1683.City skyline 城市地平线

    传送门 从左到右扫一遍,考虑什么时候会和之前形成同一幢房子从而不用统计 显然是当前的高度和之前某个点高度相同,并且它们之间没有更矮的建筑 考虑用一个单调栈维护一个单调上升的房子轮廓,然后对于扫到的每一 ...

  8. 监控软件之open-falcon

    一.open-falcon介绍 1)中文社区介绍 http://book.open-falcon.org/zh_0_2/intro/ 参照文档: https://www.cnblogs.com/LAl ...

  9. <一> idea+gradle+springboot创建项目

    转载自https://windcoder.com/springbootchutan-chuangjianxiangmu 前言 一边学习公司用到的技术,一边重构小程序后端,从而更好的理解公司搭建的框架. ...

  10. this的指向问题 call apply bind 中的this

    在函数中this指向谁:     函数中的this指向谁,是由函数被调用的那一刻就确定下来的 平时确定一个函数中的this是谁,我们需要通过调用模式来确定 1. 函数调用模式 this ---> ...