1、模式的定义与特点

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

访问者(Visitor)模式是一种对象行为型模式,

其主要优点如下

扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

主要缺点如下

增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

2、示例代码

定义Element接口

  1.  package cn.design.behavior.visitor;
     
     /**
      * @author lin
      * @version 1.0
      * @date 2020-07-27 16:15
      * @Description TODO
      */
     public interface Element {
         void accept(Visitor visitor);
     }
     

定义Compute类

  1.  package cn.design.behavior.visitor;
     
     import cn.design.create.abstractfactory.clone.User;
     
     /**
      * @author lin
      * @version 1.0
      * @date 2020-07-27 16:33
      * @Description TODO
      */
     public class Compute implements Element {
         User user = new User();
         int cpu = 300;
         int board = 150;
         int memory = 50;
     
         @Override
         public void accept(Visitor visitor) {
             visitor.visit(this);
        }
     
         /**
          * 原始方法
          */
         public void visit() {
             // 如果为学生
             if (user.obj.equals("stu")) {
                 this.cpu -= 20; 
                this.board -= 20; 
                this.memory -= 20; 
                System.out.println("全系优惠20元," + this.toString()); 
           } else if (user.obj.equals("adu")) { 
                this.cpu -= 10; 
                this.board -= 10; 
                this.memory -= 10; 
                System.out.println("全系优惠10元," + this.toString()); 
           } else { 
                this.cpu -= 5; 
                this.board -= 5; 
                this.memory -= 5; 
                System.out.println("全系优惠10元," + this.toString()); 
           } 
       }  
  2.  
  3.     @Override 
        public String toString() { 
            return "Compute{" + 
                    "cpu=" + cpu + 
                    ", board=" + board + 
                    ", memory=" + memory + 
                    '}'; 
       }  
  4.  
  5. } 

定义Visitor接口

  1.  package cn.design.behavior.visitor;
     
     /**
      * @author lin
      * @version 1.0
      * @date 2020-07-27 16:18
      * @Description TODO
      */
     public interface Visitor {
     
         void visit(Compute e);
     
     }
     

定义StudentVisitor类

  1.  package cn.design.behavior.visitor;
     
     /**
      * @author lin
      * @version 1.0
      * @date 2020-07-27 16:24
      * @Description 学生访问者
      */
     public class StudentVisitor implements Visitor {
         @Override
         public void visit(Compute element) {
             element.cpu -= 20;
             element.board -= 20;
             element.memory -= 20;
             System.out.println("全系优惠20元," + element.toString());
        }
     }
     

定义AdultVisitor类

  1.  package cn.design.behavior.visitor;
     
     /**
      * @author lin
      * @version 1.0
      * @date 2020-07-27 16:25
      * @Description 成人 访问者
      */
     public class AdultVisitor implements Visitor {
         @Override
         public void visit(Compute element) {
     
             element.cpu -= 10;
             element.board -= 10;
             element.memory -= 10;
             System.out.println("全系优惠10元," + element.toString());
        }
     }
     

定义VisitorMain类

  1.  package cn.design.behavior.visitor;
     
     
     import cn.design.create.abstractfactory.clone.User;
     
     /**
      * @author lin
      * @version 1.0
      * @date 2020-07-27 14:40
      * @Description VJTe 访问者
      */
     public class VisitorMain {
         public static void main(String[] args) {
             User user = new User("小花", 15, "stu");
             // 1 通过if 方式
             Compute compute = new Compute();
             compute.user = user;
             compute.visit();
     
             // 2 通过 访问者
             Compute compute2 = new Compute();
             compute2.user = user;
             StudentVisitor v2 = new StudentVisitor(); 
            v2.visit(compute2); 
            AdultVisitor v3 = new AdultVisitor(); 
            Compute compute3 = new Compute(); 
            compute3.user = user; 
            v3.visit(compute3); 
       } 
    } 

运行结果如下

  1.  全系优惠20元,Compute{cpu=280, board=130, memory=30}
     全系优惠20元,Compute{cpu=280, board=130, memory=30}
     全系优惠10元,Compute{cpu=290, board=140, memory=40}

3、抽象语法树之asm

详细去看 https://asm.org/ 官网

  1.  ASM 库就是 Visitor 模式的典型应用。

3.1 ASM 中几个重要的类

ClassReader

将字节数组或者 class 文件读入到内存当中,并以树的数据结构表示,树中的一个节点代表着 class 文件中的某个区域可以将 ClassReader 看作是 Visitor 模式中的访问者的实现类

ClassVisitor(抽象类)

ClassReader 对象创建之后,调用 ClassReader#accept() 方法,传入一个 ClassVisitor 对象。在 ClassReader 中遍历树结构的不同节点时会调用 ClassVisitor 对象中不同的 visit() 方法,从而实现对字节码的修改。在 ClassVisitor 中的一些访问会产生子过程,比如 visitMethod 会产生 MethodVisitor 的调用,visitField 会产生对 FieldVisitor 的调用,用户也可以对这些 Visitor 进行自己的实现,从而达到对这些子节点的字节码的访问和修改。

在 ASM 的访问者模式中,用户还可以提供多种不同操作的 ClassVisitor 的实现,并以责任链的模式提供给 ClassReader 来使用,而 ClassReader 只需要 accept 责任链中的头节点处的 ClassVisitor。

ClassWriter

ClassVisitor 的实现类,它是生成字节码的工具类,它一般是责任链中的最后一个节点,其之前的每一个 ClassVisitor 都是致力于对原始字节码做修改,而 ClassWriter 的操作则是老实得把每一个节点修改后的字节码输出为字节数组。

3.2 ASM 的工作流程

  1.  ClassReader 读取字节码到内存中,生成用于表示该字节码的内部表示的树,ClassReader 对应于访问者模式中的元素
     组装 ClassVisitor 责任链,这一系列 ClassVisitor 完成了对字节码一系列不同的字节码修改工作,对应于访问者模式中的访问者 Visitor
     然后调用 ClassReader#accept() 方法,传入 ClassVisitor 对象,此 ClassVisitor 是责任链的头结点,经过责任链中每一个 ClassVisitor 的对已加载进内存的字节码的树结构上的每个节点的访问和修改
     最后,在责任链的末端,调用 ClassWriter 这个 visitor 进行修改后的字节码的输出工作

3.3自定义ClassPrinter类

  1.  package cn.design.behavior.visitor;
     
     import jdk.internal.org.objectweb.asm.ClassReader;
     import jdk.internal.org.objectweb.asm.ClassVisitor;
     import jdk.internal.org.objectweb.asm.FieldVisitor;
     import jdk.internal.org.objectweb.asm.MethodVisitor;
     
     import java.io.IOException;
     import java.lang.reflect.Field;
     
     import static jdk.internal.org.objectweb.asm.Opcodes.ASM4;
     
     /**
      * @author lin
      * @version 1.0
      * @date 2020-07-27 17:00
      * @Description TODO
      */
     public class ClassPrinter extends ClassVisitor {
         public ClassPrinter() { 
            super(ASM4); 
       }  
  2.  
  3.     @Override 
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 
            System.out.println(name + "extends " + superName + "{"); 
       }  
  4.  
  5.     @Override 
        public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { 
            System.out.println("   " + name); 
            return null; 
       }  
  6.  
  7.     @Override 
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { 
            System.out.println("   " + name + " () "); 
            return null; 
       }  
  8.  
  9.     @Override 
        public void visitEnd() { 
            System.out.println("}"); 
       }  
  10.  
  11.     public static void main(String[] args) throws IOException { 
            ClassPrinter cp = new ClassPrinter(); 
            // 第一种方式 内部 反射 
            ClassReader cr = new ClassReader("java.lang.String"); 
            // 第二章方式 .class 方式 
            ClassReader cr3 = new ClassReader(ClassPrinter.class.getClassLoader().getResourceAsStream("cn/design/behavior/visitor/Compute.class")); 
            cr.accept(cp, 0); 
            cr3.accept(cp, 0); 
       }  
  12.  
  13. }

运行结果如下

java/lang/Stringextends java/lang/Object{   value    .......    valueOf ()    valueOf ()    intern ()    compareTo ()    <clinit> () }cn/design/behavior/visitor/Computeextends java/lang/Object{   user   cpu   board   memory    <init> ()    accept ()    visit ()    toString () }

进程已结束,退出代码0

4、Visitor 模式

我们需要为各个类逐一定义 accept 这一通用的方法作为替代。另一方面,具体的处理操作将由 Visitor 类型的对象中的方法实现。accept 方法将接收该对象作为参数,选择该对象中合适的 visit 方法并执行。这样一来,我们只需更改传递给 accept 方法的 Visitor 对象,就能轻松替换抽象语法树各节点类的处理操作。这就是 visitor 模式的核心思想。图 19.4 中的虚线表示由参数接收的 Visitor 对象的替换操作。

  1.  EvalVisitor v = new EvalVisitor();
     t.accept(v);
     int res = v.result;
     

根据 visitor 模式改写抽象语法树的节点类

  1.  public abstract class ASTree {
     public abstract void accept(Visitor v) throws Exception;
     }
     
     public class NumberLiteral extends ASTree {
        public Token value;
        public void accept(Visitor v) throws Exception {
            v.visit(this);
        }
     }
     public class BinaryExpr extends ASTree {
        public Token operator;
        public ASTree left, right;
        public void accept(Visitor v) throws Exception {
            v.visit(this);
        }
     }

Visitor 接口(Visitor.java)

  1.  public interface Visitor {
        void visit(NumberLiteral n) throws Exception;
        void visit(BinaryExpr e) throws Exception;
     }

EvalVisitor 类(EvalVisitor.java)

  1.  public class EvalVisitor implements Visitor {
        public int result;
        public void visit(NumberLiteral num) {
            result = num.value.getNumber();
        }
        public void visit(BinaryExpr e) throws Exception {
            String op = e.operator.getText();
            e.left.accept(this);
            int r1 = result;
            e.right.accept(this);
            if ("+".equals(op))
                result = r1 + result;
            else if ("*".equals(op))
                result = r1 * result;
            else
                throw new Exception("bad operator " + op); 
       } 
    }

泛型

  1.  public interface Visitor<R> {
        R visit(NumberLiteral n) throws Exception;
        R visit(BinaryExpr e) throws Exception;
     }
  1.  public <R> R accept(Visitor<R> v) throws Exception {
        return v.visit(this);
     }
  1.  public class EvalVisitor implements Visitor<Integer> { ... }

LookupVisitor 类(LookupVisitor.java)

  1.  public class LookuplVisitor implements Visitor {
        private Symbols symbols;
        public LookuplVisitor(Symbols syms) { symbols = syms; }
        public void visit(NumberLiteral num) {
            symbols.put(num.value.getText());
        }
        public void visit(BinaryExpr e) throws Exception {
            e.left.accept(this);
            e.right.accept(this);
        }
     }

5、小结

应用场景

通常在以下情况可以考虑使用访问者(Visitor)模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。

  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

访问者模式是一个非常有意思的模式,因为自己需要得到数据就需要向被访者索取,如果能够一次索取成功,访问就结束了,如果还需要其他信息,则再次向被访问者索取,就这样知道拿到自己需要的所有数据。

公众号发哥讲

这是一个稍偏基础和偏技术的公众号,甚至其中包括一些可能阅读量很低的包含代码的技术文,不知道你是不是喜欢,期待你的关注。

如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈~

● 扫码关注我们

据说看到好文章不推荐的人,服务器容易宕机!

本文版权归发哥讲博客园共有,原创文章,未经允许不得转载,否则保留追究法律责任的权利。

 

13、Visitor 访问者模式 访问数据结构并处理数据 行为型设计模式的更多相关文章

  1. Visitor 访问者模式 MD

    访问者模式 简介 访问者模式是设计模式中相对比较复杂的一个,项目中可能见得非常少. 定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下,定义作用于这些元素的新的操作. 表示 ...

  2. 设计模式-(13)访问者模式 (swift版)

    一,概念 访问者模式,是行为型设计模式之一.访问者模式是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,大多数情况下,你并不需要使用访问者 ...

  3. 浅谈设计模式-visitor访问者模式

    先看一个和visitor无关的案例.假设你现在有一个书架,这个书架有两种操作,1添加书籍2阅读每一本书籍的简介. //书架public class Bookcase { List<Book> ...

  4. 设计模式23:Visitor 访问者模式(行为型模式)

    Visitor 访问者模式(行为型模式) 动机(Motivation)在软件构造过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的修改,将会给子类带来繁重的 ...

  5. 设计模式C++学习笔记之十八(Visitor访问者模式)

      18.1.解释 概念:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. main(),客户 IVisitor,访问者接口 CBaseVis ...

  6. C++设计模式-Visitor访问者模式

    #include <iostream> #include <string> #include <string.h> #include <memory> ...

  7. 设计模式学习笔记——Visitor 访问者模式

    1.定义IVisitor接口,确定变化所涉及的方法 2.封装变化类.实现IVisitor接口 3.在实体类的变化方法中传入IVisitor接口,由接口确定使用哪一种变化来实现(封装变化) 4.在使用时 ...

  8. 第23章 访问者模式(Visitor Pattern)

    原文 第23章 访问者模式(Visitor Pattern) 访问者模式 导读:访问者模式是我个人认为所有行为模式中最为复杂的一种模式了,这个模式可能看一遍会看不懂,我也翻了好几个例子,依然不能很好的 ...

  9. 访问者(Visitor)模式

    http://www.cnblogs.com/zhenyulu/articles/79719.html 一. 访问者(Visitor)模式 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作. ...

随机推荐

  1. 机器学习实战基础(十四):sklearn中的数据预处理和特征工程(七)特征选择 之 Filter过滤法(一) 方差过滤

    Filter过滤法 过滤方法通常用作预处理步骤,特征选择完全独立于任何机器学习算法.它是根据各种统计检验中的分数以及相关性的各项指标来选择特征 1 方差过滤 1.1 VarianceThreshold ...

  2. 从零开始学Electron笔记(五)

    在之前的文章我们介绍了一下Electron的右键菜单的制作,接下来我们继续说一下Electron如何通过链接打开浏览器和嵌入网页. 现在有这样一个需求,我们要在我们的软件中加一个链接,然后点击该链接打 ...

  3. kubernetes系列(十六) - Helm安装和入门

    1. helm简介 1.1 为什么需要helm 1.2 helm中几个概念 1.3 helm用途 2. helm安装 3. helm的基本使用 3.1 安装chart仓库里面的chart 3.2 创建 ...

  4. 毕业三年从月薪6K到20K

    首先,声明这不是标题党,是一个真实的北漂故事!     为什么写这篇文章呢?第一,有感而发,感恩遇到的人和事,其次,希望对读这篇文章的你有所帮助 毕业那年 时间追溯到17年6月30号,那天毕业典礼,之 ...

  5. ubuntu 下安装QQ TIM QQ轻聊版 微信 Foxmail 百度网盘 360压缩 WinRAR 迅雷极速版

    第1步,安装deepin-wine环境:上https://github.com/wszqkzqk/deepin-wine-ubuntu页面下载zip包(或用git方式克隆),解压到本地文件夹,在文件夹 ...

  6. Go Pentester - HTTP Servers(3)

    Building Middleware with Negroni Reasons use middleware, including logging requests, authenticating ...

  7. Ethical Hacking - GAINING ACCESS(23)

    CLIENT SIDE ATTACK - BeEF Framework Hooking targets using MITMF Tools: MITMF and BeEF Start BeEF and ...

  8. echarts 实战 : 想让图例颜色和元素颜色对应,怎么办?

    首先,在 series 里设置颜色. (我是用js生成 echarts 需要的option对象,所以可能很难看懂) normalData.sData.forEach((item, index) =&g ...

  9. web自动化 -- 三种等待方式

    一.强制等待 二.隐式等待 注:隐式等待的作用域是全局,所以一般设置在整局代码的头几行. 如: 三.显示等待 元素存在: 元素可见: 元素可点击: 看到上图源码中有一个   element.is_en ...

  10. jQuery中常用网页效果应用

    一.常用网页效果应用 1.表单应用 表单由表单标签.表单域和表单按钮组成. 1.1单行文本框应用 例:获取和失去焦点改变样式 首先,在网页中创建一个表单,HTML代码如下 <form actio ...