基本需求:

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

基本介绍:

  • 在访问者模式(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. [C#.NET 拾遗补漏]10:理解 volatile 关键字

    要理解 C# 中的 volatile 关键字,就要先知道编译器背后的一个基本优化原理.比如对于下面这段代码: public class Example { public int x; public v ...

  2. equals()方法和hashCode()方法详解

    equals()方法和hashCode()方法详解 1. Object类中equals()方法源代码如下所示: /** * Object类中的equals()方法 */ public boolean ...

  3. Django中间件(Middleware)处理请求

    关注公众号"轻松学编程"了解更多. 1.面向切面编程 切点(钩子) 切点允许我们动态的在原有逻辑中插入一部分代码 在不修改原有代码的情况下,动态注入一部分代码 默认情况,不中断传播 ...

  4. 文件流转blob并播放

    axios 这里是请求了个mp3做例子: this.$axios({ methods:"GET", url:"/api/music/soures/双笙.mp3" ...

  5. Java_面向对象三大特征

    面向对象特征 面向对象三大特征: 继承, 封装, 多态 继承 继承: 子类可以从父类继承属性和方法 对外公开某些属性和方法 要点(eclipse中Ctrl+T查看继承结构) 1.父类也称超类, 基类, ...

  6. 懒得写文档,swagger文档导出来不香吗

    导航 前言 离线文档 1 保存为html 2 导出成pdf文档 3 导出成Word文档 参考 前言   早前笔者曾经写过一篇文章<研发团队,请管好你的API文档>.团队协作中,开发文档的重 ...

  7. C# 字符串处理类

    using System;using System.Collections.Generic;using System.Text;using System.Text.RegularExpressions ...

  8. C++ 基础 4:继承和派生

    1 继承和派生 在 C++ 中 可重用性是通过继承这一机制实现的.继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易.这样做,也达到了重用代码功能和提高执行效率的效果. 当创 ...

  9. QQ群web前端分析二——第一印象

    对QQ群WEB进行前端分析 入口是 http://qun.qzone.qq.com/ 以下为第一印象,主要是从我的理解上找问题. ----------------------------------- ...

  10. [MIT6.006] 1. Algorithmic Thinking, Peak Finding 算法思维,峰值寻找

    [MIT6.006] 系列笔记将记录我观看<MIT6.006 Introduction to Algorithms, Fall 2011>的课程内容和一些自己补充扩展的知识点.该课程主要介 ...