设计模式:访问者(Visitor)模式

一、前言

   什么叫做访问,如果大家学过数据结构,对于这点就很清晰了,遍历就是访问的一般形式,单独读取一个元素进行相应的处理也叫作访问,读取到想要查看的内容+对其进行处理就叫做访问,那么我们平常是怎么访问的,基本上就是直接拿着需要访问的地址(引用)来读写内存就可以了。

   为什么还要有一个访问者模式呢,这就要放到OOP之中了,在面向对象编程的思想中,我们使用类来组织属性,以及对属性的操作,那么我们理所当然的将访问操作放到了类的内部,这样看起来没问题,但是当我们想要使用另一种遍历方式要怎么办呢,我们必须将这个类进行修改,这在设计模式中是大忌,在设计模式中就要保证,对扩展开放,对修改关闭的开闭原则。

   因此,我们思考,可不可以将访问操作独立出来变成一个新的类,当我们需要增加访问操作的时候,直接增加新的类,原来的代码不需要任何的改变,如果可以这样做,那么我们的程序就是好的程序,因为可以扩展,符合开闭原则。而访问者模式就是实现这个的,使得使用不同的访问方式都可以对某些元素进行访问。

二、代码

Element 接口:
1 package zyr.dp.visitor;
2
3 public interface Element {
4
5     public abstract void accept(Visitor visitor);
6
7 }

Entry 类:
 1 package zyr.dp.visitor;
 2
 3 import java.util.Iterator;
 4
 5 public abstract class Entry implements Element{
 6     public abstract String getName();
 7     public abstract int getSize();
 8     public abstract void printList(String prefix);
 9     public  void printList(){
10         printList("");
11     }
12     public  Entry add(Entry entry) throws RuntimeException{
13         throw new RuntimeException();
14     }
15     public  Iterator iterator() throws RuntimeException{
16         throw new RuntimeException();
17     }
18     public  String toString(){
19         return getName()+"<"+getSize()+">";
20     }
21 }

File 类:
 1 package zyr.dp.visitor;
 2
 3 public class File extends Entry {
 4
 5     private String name;
 6     private int size;
 7     public File(String name,int size){
 8         this.name=name;
 9         this.size=size;
10     }
11     public String getName() {
12         return name;
13     }
14
15     public int getSize() {
16         return size;
17     }
18
19     public void printList(String prefix) {
20         System.out.println(prefix+"/"+this);
21     }
22     public void accept(Visitor visitor) {
23        //  System.out.println("开始访问文件:"+this);
24         visitor.visit(this);
25        // System.out.println("结束访问文件:"+this);
26        // System.out.println();
27     }
28
29 }

 Directory类:

 1 package zyr.dp.visitor;
 2
 3 import java.util.ArrayList;
 4 import java.util.Iterator;
 5
 6 public class Directory extends Entry {
 7
 8     String name;
 9     ArrayList entrys=new ArrayList();
10     public Directory(String name){
11         this.name=name;
12     }
13     public String getName() {
14         return name;
15     }
16
17     public int getSize() {
18         int size=0;
19         Iterator it=entrys.iterator();
20         while(it.hasNext()){
21             size+=((Entry)it.next()).getSize();
22         }
23         return size;
24     }
25
26     public Entry add(Entry entry) {
27         entrys.add(entry);
28         return this;
29     }
30
31     public Iterator iterator() {
32         return entrys.iterator();
33     }
34
35     public void printList(String prefix) {
36         System.out.println(prefix+"/"+this);
37         Iterator it=entrys.iterator();
38         Entry entry;
39         while(it.hasNext()){
40             entry=(Entry)it.next();
41             entry.printList(prefix+"/"+name);
42         }
43     }
44     public void accept(Visitor visitor) {
45       //  System.out.println("开始访问文件夹:"+this);
46         visitor.visit(this);
47      //   System.out.println("结束访问文件夹:"+this);
48      //   System.out.println();
49     }
50
51 }

Visitor 类:
1 package zyr.dp.visitor;
2
3 public abstract class Visitor {
4
5     public abstract void visit(File file);
6     public abstract void visit(Directory directory);
7
8 }

  ListVisitor类:

 1 package zyr.dp.visitor;
 2
 3 import java.util.Iterator;
 4
 5 public class ListVisitor extends Visitor {
 6
 7     String currentDir = "";
 8     public void visit(File file) {
 9         System.out.println(currentDir+"/"+file);
10     }
11
12     public void visit(Directory directory) {
13         System.out.println(currentDir+"/"+directory);
14         String saveDir=currentDir;
15         currentDir+=("/"+directory.getName());
16         Iterator it=directory.iterator();
17         while(it.hasNext()){
18             Entry entry=(Entry)it.next();
19             entry.accept(this);
20         }
21         currentDir=saveDir;
22     }
23
24 }

FileVisitor 类:
 1 package zyr.dp.visitor;
 2
 3 import java.util.ArrayList;
 4 import java.util.Iterator;
 5
 6 public class FileVisitor extends Visitor {
 7
 8     String currentDir = "";
 9     String suffix;
10     ArrayList files=new ArrayList();
11
12     public FileVisitor(String suffix){
13          this.suffix = suffix;
14     }
15
16     public void visit(File file) {
17         if(file.getName().endsWith(suffix)){
18          // System.out.println(currentDir+"/"+file);
19             files.add(currentDir+"/"+file);
20         }
21     }
22
23     public void visit(Directory directory) {
24         String saveDir=currentDir;
25         currentDir+=("/"+directory.getName());
26         Iterator it=directory.iterator();
27         while(it.hasNext()){
28             Entry entry=(Entry)it.next();
29             entry.accept(this);
30         }
31         currentDir=saveDir;
32     }
33     Iterator getFiles(){
34         return files.iterator();
35     }
36
37 }

 Main类:

 1 package zyr.dp.visitor;
 2
 3 import java.util.Iterator;
 4
 5
 6 public class Main {
 7
 8     public static void main(String[] args) {
 9
10         Directory root=new Directory("根目录");
11
12         Directory life=new Directory("我的生活");
13         File eat=new File("吃火锅.txt",100);
14         File sleep=new File("睡觉.html",100);
15         File study=new File("学习.txt",100);
16         life.add(eat);
17         life.add(sleep);
18         life.add(study);
19
20         Directory work=new Directory("我的工作");
21         File write=new File("写博客.doc",200);
22         File paper=new File("写论文.html",200);
23         File homework=new File("写家庭作业.docx",200);
24         work.add(write);
25         work.add(paper);
26         work.add(homework);
27
28         Directory relax=new Directory("我的休闲");
29         File music=new File("听听音乐.js",200);
30         File walk=new File("出去转转.psd",200);
31         relax.add(music);
32         relax.add(walk);
33
34         Directory read=new Directory("我的阅读");
35         File book=new File("学习书籍.psd",200);
36         File novel=new File("娱乐小说.txt",200);
37         read.add(book);
38         read.add(novel);
39
40         root.add(life);
41         root.add(work);
42         root.add(relax);
43         root.add(read);
44
45         root.accept(new ListVisitor());
46         System.out.println("========================");
47         FileVisitor visitor=new FileVisitor(".psd");
48         root.accept(visitor);
49         Iterator it = visitor.getFiles();
50         while(it.hasNext()){
51             System.out.println(it.next());
52         }
53
54     }
55
56 }

 运行结果:

  可以看到我们的运行结果第一个和使用Composite模式的结果一样,第二个是实现另一种方式的访问,只访问文件后缀为某一特定的内容的文件,结果也是正确的,并且为了说明我们的访问还可以保存下来访问的结果,我们使用了ArrayList自带的迭代器将保存到ArrayList中的结果输出出来,我们当然也可以直接在遍历的时候就输出出来,这个看我们的使用要求了。由此可以看到在保证数据结构(File和Directory)不发生变化的情况下(没有新增或者删除),可以非常方便增加新的一种访问方法,只需要新增加一个访问类即可,但是如果我们数据结构发生变化之后,就需要修改继承自Visitor类的所有类了,这也违背了开闭原则,因此我们应该认真考虑,到底我们的数据结构是定死的还是经常变化的。没有任何一种设计模式是十全十美的,总是有所取舍,有所利弊,根据实际情况来选择才是最好的设计方法。

   这里要说明一下双重分发机制,我们来看一下最核心的遍历逻辑,结合组合模式的时候我们已经分析过的遍历方法,递归,大家觉得这次我们要怎么在数据结构外面进行遍历,肯定还是要使用递归了,可是数据结构中的数据在类的内部,怎么递归到内部呢,我们想到了间接递归,也就是双重分发。

1     public void printList(String prefix) {
2         System.out.println(prefix+"/"+this);
3         Iterator it=entrys.iterator();
4         Entry entry;
5         while(it.hasNext()){
6             entry=(Entry)it.next();
7             entry.printList(prefix+"/"+name);
8         }
9     }

上面的代码是在组合模式类的内部遍历的过程,可以明确的看到递归(直接递归)的使用。我们看一下访问者模式中的间接递归:

 Directory类中:

    public void accept(Visitor visitor) {//System.out.println("开始访问文件夹:"+this);visitor.visit(this);//System.out.println("结束访问文件夹:"+this);//System.out.println();}

 File类:

1     public void accept(Visitor visitor) {
2         //System.out.println("开始访问文件:"+this);
3         visitor.visit(this);
4         //System.out.println("结束访问文件:"+this);
5         //System.out.println();
6     }

 在ListVisitor中:

 1     public void visit(File file) {2         System.out.println(currentDir+"/"+file);3     }
 4
 5     public void visit(Directory directory) {
 6         System.out.println(currentDir+"/"+directory);
 7         String saveDir=currentDir;
 8         currentDir+=("/"+directory.getName());
 9         Iterator it=directory.iterator();
10         while(it.hasNext()){
11             Entry entry=(Entry)it.next();
12             entry.accept(this);
13         }
14         currentDir=saveDir;
15     }

  我们看到了entry.accept(this)这句话,这句话是非常重要的,我们在Main中是这样用的:

1     root.accept(new ListVisitor());

  那么串连起来,在Main中我们通过Directory或者File类型的对象调用accept(访问者)方法,接受访问者的访问,这是访问者和被访问者的第一次亲密接触,亲近对方就是为了获得对方的数据,然后才能对对方的数据进行使用,那么怎么拿到的呢?!我们看到了这句visitor.visit(this);这句话无疑是重要的,被调用者告诉访问者,我将我的内容this,全部给你了,以后访问者就可以对this所指代的被访问者的内容进行操作了,分为两类,如果被访问者是File文件类型的,就会直接输出内容,到达叶子结点,访问结束;如果是文件夹,那就非常有意思了,首先我们仍旧是让被访问者将自己的内容交给访问者visitor.visit(this);,之后public void visit(Directory directory)被调用,通过遍历的方式将属于这个文件夹下面的数据全部拿到Iterator it=directory.iterator();,然后开始一个个的处理,怎么处理呢,继续访问属于这个文件夹下面对象的accept()方法使用entry.accept(this);,来将访问者交过去,交给谁?!肯定是给entry所指的对象,也就是文件夹里面的子文件夹或者文件,如果是文件的话,继续在自己的方法中调用visitor.visit(this);,最终落实到调用 public void visit(File file)通过System.out.println(currentDir+"/"+file);访问结束,如果不是文件呢?若为文件夹,则继续调用属于文件夹的方法,就这样不断地往下面查找,一直到遍历完文件夹下面的所有的元素,因此也是深度优先遍历。就这样通过压栈和出栈,我们完成了最终的遍历,最终的出口有两个,一个是访问文件,输出之后结束,另一个是遍历完文件夹,即使文件夹下面没有文件依旧结束。

1 root.accept(new ListVisitor());

1     public void accept(Visitor visitor) {
2         visitor.visit(this);
3     }

 1     public void visit(File file) {
 2         System.out.println(currentDir+"/"+file);
 3     }
 4
 5     public void visit(Directory directory) {
 6         System.out.println(currentDir+"/"+directory);
 7         String saveDir=currentDir;
 8         currentDir+=("/"+directory.getName());
 9         Iterator it=directory.iterator();
10         while(it.hasNext()){
11             Entry entry=(Entry)it.next();
12             entry.accept(this);
13         }
14         currentDir=saveDir;
15     }

  在accept函数中调用visit,同样在visit中调用accept,这就是间接递归,或者叫做双重分发。产生的原因就是访问者需要和被访问者相互交流,才能一步步的得到想要的数据。我们可以考虑主持人采访一个明星,那么这个明星接受采访,把自己基本信息(能问的问题以及某些答案)告诉主持人,问主持人有问题吗?如果主持人有问题(还能向下问)要问那么就再次拿着新的问题问这个明星,这个明星再次将自己关于这方面的信息告诉主持人;如果没有问题(得到答案),主持人将信息总结之后说出来。就这样一直持续下去,直到主持人没问题问了,并且明星的信息也都被问到了,这样采访就结束了。由此可见,很多时候设计模式都是和生活密切相关的,生活中的常识有时候就是一些套路,而这种套路就是一种抽象的模式。

三、总结

  访问者模式是一个非常有意思的模式,因为自己需要得到数据就需要向被访者索取,如果能够一次索取成功,访问就结束了,如果还需要其他信息,则再次向被访问者索取,就这样知道拿到自己需要的所有数据。在本例中借用了组合模式中的数据结构,那是因为这种树形的结构很适合我们进行递归访问。访问者模式和迭代器模式都是在某种数据结构上进行处理,一种是对数据结构中的元素进行某种特定的处理,另一种是用某种方式遍历所有元素。在实际应用中,我们根据实际需要来考虑是不是需要双重分发机制。在本例中的访问者模式中用到了组合模式、委托(组合)、双重分发等原理,便于新增访问方式,不便于对数据结构的修改。

  程序代码

转载于:https://www.cnblogs.com/zyrblog/p/9244754.html

设计模式:访问者(Visitor)模式相关推荐

  1. 【鲁班学院】设计模式—访问者(Visitor)模式

    一.定义 访问者模式是一种从操作的对象结构中分离算法的方式. 它可以在不改变数据结构的前提下定义作用与这些元素的新操作.它遵循开闭原则. Represent an operation to be pe ...

  2. 设计模式学习笔记——访问者(Visitor)模式

    设计模式学习笔记--访问者(Visitor)模式 @(设计模式)[设计模式, 访问者模式, visitor] 设计模式学习笔记访问者Visitor模式 基本介绍 访问者案例 类图 实现代码 Visit ...

  3. 设计模式学习笔记--访问者(Visitor)模式

    写在模式学习之前 什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方案,这就是软件模式:每一个模式描述了一个在我们程序设计中经常发生的问题,以及该问题的解决方案:当我们碰到模式 ...

  4. java的visitor模式_Java设计模式之Visitor模式是什么样的?

    作用于某个对象群中各个对象的操作.它可以使你在不改变这些对象本身的情况下,定义作用于这些对象的新操作.在Java中,Visitor模式实际上是分离了collection结构中的元素和对这些元素进行操作 ...

  5. 设计模式:Visitor模式

    Visitor模式是一个用起来很简单,理解起来可能稍微有一点困难的模式.不过明白了之后就清楚了,其实也是非常的简单. 问题 需要向对象结构中增加新的方法,但是增加起来会很费劲或者会破坏设计. 案例 举 ...

  6. 【设计模式】行为模式之Visitor访问者

    Visitor访问者是一种对象行为型设计模式,用于表示一个作用于某对象结构中的各元素的操作,使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作.使用Visitor模式, 必须定义两个类层次, ...

  7. C++设计模式——访问者模式(visitor pattern)

    一.原理讲解 1.1意图 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 1.2应用场景 一个对象结构包含很多类对象,它们有不同的接口,而你 ...

  8. Visitor模式(访问者设计模式)

    Visitor ? 在Visitor模式中,数据结构与处理被分离开来.我们编写一个表示"访问者"的类来访问数据结构中的元素, 并把对各元素的处理交给访问者类.这样,当需要增加新的处 ...

  9. 23中设计模式之访问者visitor设计模式

    " 访问者设计模式学习心得分享" 适用于结构相对稳定的条目或者xxx对比时,其它相对稳定的层级组织架构使用该模式是OK滴 如条目中文件和文件夹的访问,男人和女人对同一件事情不同反应 ...

  10. java visitor_Java设计模式(三) Visitor(访问者)模式及多分派场景应用

    基本概念 Visitor 封装一些作用于数据结构中的各元素的操作,不同的操作可以借助新的visitor实现,降低了操作间的耦合性 访问者可以将数据结构和对数据的操作解耦,使得增加对数据结构的操作不需要 ...

最新文章

  1. andriod studio 注释乱码问题
  2. 虚拟键码 键盘消息(初稿)
  3. 2020-11-23(Windows系统的dll注入 )
  4. BZOJ 1101 Luogu P3455 POI 2007 Zap (莫比乌斯反演+数论分块)
  5. eclipse源服务器未能找到,eclipse - HTTP状态[404]? [未找到](原始服务器未找到当前表示) - 堆栈内存溢出...
  6. linux隐藏apache信息,Linux下如何隐藏Apache版本号信息
  7. 卡巴斯基:风险无国界 网络安全从业者要与小网民保持一致
  8. express项目搭建 初始化详细步骤
  9. junit 单元测试报错java.lang.NoClassDefFoundError
  10. python+opencv中imread函数第二个参数的含义
  11. Tornado快速入门
  12. Leetcode每日一题:27.remove-element(移除元素)
  13. Tomcat 指定jdk
  14. 为GitHub项目加入Travis-CI的自动集成
  15. 局域网搭建git服务器
  16. HTTP协与Apache服务的搭建
  17. 闽南歌歌词有一句电子计算机,丁丁丁丁是什么歌_抖音闽南歌曲丁丁丁丁歌名、歌手、歌词介绍_游戏吧...
  18. Ddos攻击怎么防护?DDOS八大防御策略
  19. YAPI,一个神奇的接口管理平台
  20. MySQL-3个表连接、左连接、右连接-2021/09/06

热门文章

  1. MySQL_Workbench使用
  2. 计算机三级分类汇总,计算机三级网络考试机试100道分类汇总-整数各位数字运算排序统计.doc...
  3. java chunked 解码_模拟http请求 带 chunked解析办法一
  4. 数字经济时代下老年群体手机APP软件网络推广适老化需求日益明显
  5. 网站优化期间有哪些细节需要注意?
  6. grub通过img文件启动linux,Linux GRUB实现双系统引导之菜鸟教程
  7. java打印图片到页面_在Java中打印BufferedImage的正确方法
  8. java已知一个二叉树_Day58:对称的二叉树
  9. vb如何测试连接mysql_怎么在vb程序中查找数据库信息并显示
  10. verilog设计一个补码加减法运算器_漫画:为什么计算机用补码存储数据?