15.java设计模式之访问者模式
基本需求:
- 电脑需要键盘鼠标等固定的组件组成
- 现在分为个人,组织等去买电脑,而同一种组件对不同的人(访问者)做出不同的折扣,从而电脑的价格也不一样
- 传统的解决方法:在组件内部进行判断访问人的类型,从而进行不同打出不同的折扣
- 缺陷:如果访问者的类型增加了,则需要改变组件内部的判断代码,违反了开闭原则,访问者的类型太多,判断的代码也会很庞大
基本介绍:
在访问者模式(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设计模式之访问者模式的更多相关文章
- 折腾Java设计模式之访问者模式
博客原文地址:折腾Java设计模式之访问者模式 访问者模式 Represent an operation to be performed on the elements of an object st ...
- JAVA设计模式之访问者模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...
- 由电脑专卖系统引发的Java设计模式:访问者模式
目录 定义 意图 解决问题 何时使用 优缺点 结构 电脑专卖系统 定义 访问者模式是对象的行为型模式,它的目的是封装一些施加于某些数据结构元素之上的操作,一旦这些操作需要修改的话,接收这个操作的数据结 ...
- 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)
原文:乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) 作者:webabc ...
- C#设计模式总结 C#设计模式(22)——访问者模式(Vistor Pattern) C#设计模式总结 .NET Core launch.json 简介 利用Bootstrap Paginator插件和knockout.js完成分页功能 图片在线裁剪和图片上传总结 循序渐进学.Net Core Web Api开发系列【2】:利用Swagger调试WebApi
C#设计模式总结 一. 设计原则 使用设计模式的根本原因是适应变化,提高代码复用率,使软件更具有可维护性和可扩展性.并且,在进行设计的时候,也需要遵循以下几个原则:单一职责原则.开放封闭原则.里氏代替 ...
- Java设计模式——装饰者模式
JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...
- 浅析JAVA设计模式之工厂模式(一)
1 工厂模式简单介绍 工厂模式的定义:简单地说,用来实例化对象,取代new操作. 工厂模式专门负责将大量有共同接口的类实例化.工作模式能够动态决定将哪一个类实例化.不用先知道每次要实例化哪一个类. 工 ...
- JAVA设计模式--装饰器模式
装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...
- 折腾Java设计模式之建造者模式
博文原址:折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, a ...
随机推荐
- Hbase实用技巧:全量+增量数据的迁移方法
摘要:本文介绍了一种Hbase迁移的方法,可以在一些特定场景下运用. 背景 在Hbase使用过程中,使用的Hbase集群经常会因为某些原因需要数据迁移.大多数情况下,可以跟用户协商用离线的方式进行迁移 ...
- STM32入门系列-复位程序
已经对启动文有了大致了解,再来看看系统在复位过程中做了哪些工作.复位程序如下: 1 ; Reset handler 2 3 Reset_Handler PROC 4 5 EXPORT Reset_Ha ...
- 开发工具:Mybatis.Plus.插件三种方式的逆向工程
本文源码:GitHub·点这里 || GitEE·点这里 一.逆向工程简介 在Java开发中,持久层最常用的框架就是mybatis,该框架需要编写sql语句,mybatis官方提供逆向工程,可以把数据 ...
- 处理textarea里Enter(回车换行符)
Enter换行符 如果包含有回车换行符,在字符串中表现为"\n": 会返回一条字符串: 原文章:https://blog.csdn.net/shenlf_bk/article/de ...
- IDEA(社区版)连接MySQL
版本说明: MySQL 版本:8.0.20 IDEA 版本:2020.1.1(Community Edition) IDEA中安装插件: 首先在IDEA中下载DB Browser插件,安装好插件重 ...
- 关于C中指针的引用,解引用与脱去解引用
*,& 在指针操作中的意义 (1)* 大家都知道在写int *p 时,*可以声明一个指针.很少人知道*在C/C++中还有一个名字就是"解引用".他的意思就是解释引用,说的通 ...
- Thinkphp3.2 cms之分类管理
四.分类管理 <?php namespace Admin\Controller; use Think\Controller; class CateController extends Contr ...
- C# 全局唯一标识符 (GUID)
一 C# 全局唯一标识符 (GUID) Represents a globally unique identifier (GUID). To browse the .NET Framework so ...
- 虚拟机vbox给vdi增加容量到16G后的一系列操作
虚拟机vbox给vdi增加容量到16G后的一系列操作windows 下:cmdVbox/bin下1.#VBoxManage modifyhd "cloned.vdi" --resi ...
- 经典c程序100例==81--90
[程序81] 题目:809*??=800*??+9*??+1 其中??代表的两位数,8*??的结果为两位数,9*??的结果为3位数.求??代表的两位数,及809*??后的结果. 1.程序分析: 2.程 ...