基本需求:

  • 电脑需要键盘鼠标等固定的组件组成
  • 现在分为个人,组织等去买电脑,而同一种组件对不同的人(访问者)做出不同的折扣,从而电脑的价格也不一样
  • 传统的解决方法:在组件内部进行判断访问人的类型,从而进行不同打出不同的折扣
    • 缺陷:如果访问者的类型增加了,则需要改变组件内部的判断代码,违反了开闭原则,访问者的类型太多,判断的代码也会很庞大

基本介绍:

  • 在访问者模式(Visitor)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作

  • 也就是说,被访问者可以根据不同的访问者做出不同的响应

  • 封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作

  • 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题

  • 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口

  • 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决

  • UML类图(原理)

    • 说明

      • Visitor是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作
      • ConcreteVisitor是一个具体的访问值实现每个有Visitor声明的操作,是每个操作实现的部分
      • ObjectStructure能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
      • Element定义一个accept 方法,接收一个访问者对象
      • ConcreteElement为具体元素,实现了accept方法
      • 核心思想就是:不同访问者通过accept访问相同的被访问者,被访问者根据访问者携带的方法做出具体的动作,从而达到相同的被访问者再不同的场景下做出不同的响应,对被访问者没有任何影响,也就是双分派
        • 双分派说明

          • 双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型
          • 即首先在客户端程序中,accept将具体状态作为参数传递Element中,第一次分派
          • 然后Element类调用作为参数的 "具体方法" 中方法operation1, 同时将自己(this)作为参数,完成第二次分派
          • 所以,要求了visitor必须要有第二次分派使用的方法(感觉就像踢皮球,我调用你,最终调用的还是我的方法)
  • UML类图(案例)

    • 说明

      • ComputerPart是被访问者的抽象父类,提供接受访问者的方法,Keyboard和Mouse是其具体实现
      • Visitor是访问者父类,其中包含了访问Keyboard和Mouse类的访问回调方法,所以这就要求了被访问的子类是固定的,如果不固定,增加或者删除,就要修改Visitor类中方法数量
      • Computer充当了ObjectStructure,聚合了被访问者并使用
  • 代码实现

    • public abstract class ComputerPart {
      
         // 电脑组件抽象父类 也就是被访问者 提供接收访问者方法
      
         protected String name;
      
         protected double price;
      
         public ComputerPart(String name, double price) {
      this.name = name;
      this.price = price;
      } // 接收访问者,双分派中第一次分派
      public abstract double accept(Visitor visitor); } // 子类一
      class Keyboard extends ComputerPart{ public Keyboard(String name, double price) {
      super(name, price);
      } @Override
      public double accept(Visitor visitor) {
      // 调用访问者的访问回调方法,将自身再传递给访问者,第二次分派,对不同的访问者做出不同的响应
      // 这个this是关键,也是重点
      return visitor.visitKeyboard(this);
      } } // 子类二
      class Mouse extends ComputerPart{ public Mouse(String name, double price) {
      super(name, price);
      } @Override
      public double accept(Visitor visitor) {
      // 调用访问者的访问回调方法,将自身再传递给访问者,第二次分派,对不同的访问者做出不同的响应
      // 这个this是关键,也是重点
      return visitor.visitMouse(this);
      } }
    • public interface Visitor {
      
         // 访问者抽象父接口 其中的访问回调方法(参数为具体的被访问者)需要包含所有的访问者实现
      // 所以这就要求了被访问的子类是固定的,如果不固定,增加或者删除,就要修改Visitor类中方法数量 // 访问Keyboard的访问回调 这样不同的访问者访问同一个被访问者得到的结果都不同
      double visitKeyboard(Keyboard keyboard); // 访问Mouse的访问回调
      double visitMouse(Mouse mouse); } // 子类一 个人用户 为方便都是九折
      class PersonVisitor implements Visitor { @Override
      public double visitKeyboard(Keyboard keyboard) {
      return keyboard.price * 0.9;
      } @Override
      public double visitMouse(Mouse mouse) {
      return mouse.price * 0.9;
      } } // 子类二 群体用户 为方便都是八折
      class GroupVisitor implements Visitor { @Override
      public double visitKeyboard(Keyboard keyboard) {
      return keyboard.price * 0.8;
      } @Override
      public double visitMouse(Mouse mouse) {
      return mouse.price * 0.8;
      } }
    • public class Computer {
      
         // 此类作为 ObjectStructure,聚合了被访问者并使用
      private Keyboard keyboard; private Mouse mouse; public Computer(Keyboard keyboard, Mouse mouse) {
      this.keyboard = keyboard;
      this.mouse = mouse;
      } // 根据不同的访问者获取电脑的价格
      public double getPrice(Visitor visitor) {
      // 被访问者就收访问者
      return keyboard.accept(visitor) + mouse.accept(visitor);
      } }
    • public class Client {
      public static void main(String[] args) {
      // 创建鼠标键盘和电脑
      Keyboard keyboard = new Keyboard("keyboard", 100d);
      Mouse mouse = new Mouse("mouse", 100d);
      Computer computer = new Computer(keyboard, mouse);
      // 使用不同的访问者获取电脑的价格
      PersonVisitor personVisitor = new PersonVisitor();
      System.out.println("个人获得的电脑价格是:" + computer.getPrice(personVisitor)); GroupVisitor groupVisitor = new GroupVisitor();
      System.out.println("群体获得的电脑价格是:" + computer.getPrice(groupVisitor)); // 如果还有其他的访问者,直接增加其实现类即可,不用改动其他代码,被访问者就可以做出不同的响应
      }
      }

注意事项:

  • 访问者模式符合单一职责原则,让程序有优秀的扩展性、灵活性非常高
  • 访问者模式可以对功能进行统一,可以做报表、ui、拦截器与过滤器,适用于数据结构相对稳定的系统
  • 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难
  • 违反了依赖倒转原则,访问者依赖的是具体元素,而不是抽象元素
  • 如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式是比较适合的

15.java设计模式之访问者模式的更多相关文章

  1. 折腾Java设计模式之访问者模式

    博客原文地址:折腾Java设计模式之访问者模式 访问者模式 Represent an operation to be performed on the elements of an object st ...

  2. JAVA设计模式之访问者模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...

  3. 由电脑专卖系统引发的Java设计模式:访问者模式

    目录 定义 意图 解决问题 何时使用 优缺点 结构 电脑专卖系统 定义 访问者模式是对象的行为型模式,它的目的是封装一些施加于某些数据结构元素之上的操作,一旦这些操作需要修改的话,接收这个操作的数据结 ...

  4. 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)

    原文:乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) 作者:webabc ...

  5. C#设计模式总结 C#设计模式(22)——访问者模式(Vistor Pattern) C#设计模式总结 .NET Core launch.json 简介 利用Bootstrap Paginator插件和knockout.js完成分页功能 图片在线裁剪和图片上传总结 循序渐进学.Net Core Web Api开发系列【2】:利用Swagger调试WebApi

    C#设计模式总结 一. 设计原则 使用设计模式的根本原因是适应变化,提高代码复用率,使软件更具有可维护性和可扩展性.并且,在进行设计的时候,也需要遵循以下几个原则:单一职责原则.开放封闭原则.里氏代替 ...

  6. Java设计模式——装饰者模式

    JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...

  7. 浅析JAVA设计模式之工厂模式(一)

    1 工厂模式简单介绍 工厂模式的定义:简单地说,用来实例化对象,取代new操作. 工厂模式专门负责将大量有共同接口的类实例化.工作模式能够动态决定将哪一个类实例化.不用先知道每次要实例化哪一个类. 工 ...

  8. JAVA设计模式--装饰器模式

    装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...

  9. 折腾Java设计模式之建造者模式

    博文原址:折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, a ...

随机推荐

  1. Jupyter Notebook使用教程

    关于安装我就不说了,可以参考知乎https://zhuanlan.zhihu.com/p/33105153(总结的很全面) 首先打开Jupyter Notebook后,新建notebook:点击右上角 ...

  2. java开发-前后端分离

    众所周知,做java开发是后端的开发,我们时常与前端打交道,但更加注重后端代码的实现,前台的页面都是由前端开发人员做的,那么,是怎么做到前后端分离的呢? 首先,是后端的开发, 在mapper层:Stu ...

  3. NB-IoT应用分类与技术特点分析

      NB-Iot作为一种窄带物联网技术在各大行业脱颖而出,其应用涵盖多个领域.此文计讯小编将讲解NB-IoT的主要应用分类及相关特点.   一.NB-IoT是什么   NB-IoT是指窄带物联网(Na ...

  4. Django项目登录注册系统

    Django项目之个人网站 关注公众号"轻松学编程"了解更多. Github地址:https://github.com/liangdongchang/MyWeb.git 感兴趣的可 ...

  5. uniApp朋友圈(参考)

    介绍 功能:回复,点赞(笔芯),评论,图片(最多六张). 码云地址:https://gitee.com/sunliusen/friend 例:

  6. canvas生成圆图和微信小程序canvas圆图

    先在HTML中创建 img和canvas并设置id属性 <canvas id="canvas" width="500" height="500& ...

  7. Ros中创建msg和srv遇到的问题

    在创建msg和srv文件之后,使用srv和msg文件时候需要对xml文件进行修改,如下: <build_depend>message_generation</build_depend ...

  8. Manacher (马拉车) 算法:解决最长回文子串的利器

    最长回文子串 回文串就是原串和反转字符串相同的字符串.比如 aba,acca.前一个是奇数长度的回文串,后一个是偶数长度的回文串. 最长回文子串就是一个字符串的所有子串中,是回文串且长度最长的子串. ...

  9. 解决js中对象中属性是数组中对应元素,不能使用点数组元素(.数组[i])来获取value值来循环,属性不能是数组元素array[i]的问题

    数据类型 //示例 var tags1avg= ['rg2_crt_001_001_avg', 'rg2_crt_001_002_avg', 'rg2_crt_001_003_avg', 'rg2_c ...

  10. JS基础算法题(二)

    1.1 数组去重的五种方法 数组去重:将数组中重复的元素去掉 JS数组没有删除具体元素的删除(只能删掉值,删不掉元素的索引),可以使用另外一个结构来进行存储 新数组 新对象 JS数组虽然本质可以删除第 ...