zoukankan      html  css  js  c++  java
  • 设计模式:访问者(Visitor)模式

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

    三、总结

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

      程序代码

  • 相关阅读:
    Zabbix5 Frame 嵌套
    Zabbix5 对接 SAML 协议 SSO
    CentOS7 安装 Nexus
    CentOS7 安装 SonarQube
    GitLab 后台修改用户密码
    GitLab 查看版本号
    GitLab Admin Area 500 Error
    Linux 安装 PostgreSQL
    Liger ui grid 参数
    vue.js 是一个怪东西
  • 原文地址:https://www.cnblogs.com/zyrblog/p/9244754.html
Copyright © 2011-2022 走看看