访问者模式介绍

目录

一、访问者模式介绍

1.1 访问者模式定义

1.2 访问者模式原理

1.2.1 访问者模式类图

1.2.2 模式角色说明

二、访问者模式的应用

2.1 需求说明

2.2 需求实现

2.2.1 V1版本

2.2.1.1 抽象产品类

2.2.1.2 糖果类

2.2.1.3 酒水类

2.2.1.4 水果类

2.2.1.5 访问者接口

2.2.1.6 访问者实现类

2.2.1.7 测试类

2.2.2 V2版本

2.2.2.1 接待者接口

2.2.2.2 糖果类(优化)

2.2.2.3 酒水类(优化)

2.2.2.4 水果类(优化)

2.2.2.5 测试类

三、访问者模式总结

3.1 访问者模式的优点

3.2 访问者模式的缺点

3.3 访问者模式的使用场景


一、访问者模式介绍

1.1 访问者模式定义

访问者模式(Visitor Pattern) 的原始定义是:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离。

这个定义会比较抽象,但是我们依然能看出两个关键点:

  • 一个是: 运行时使用一组对象的一个或多个操作,比如,对不同类型的文件(.pdf、.xml、.properties)进行扫描;
  • 另一个是: 分离对象的操作和对象本身的结构,比如,扫描多个文件夹下的多个文件,对于文件来说,扫描是额外的业务操作,如果在每个文件对象上都加一个扫描操作,太过于冗余,而扫描操作具有统一性,非常适合访问者模式。

访问者模式主要解决的是数据与算法的耦合问题,尤其是在数据结构比较稳定,而算法多变的情况下。为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展。

访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式。

1.2 访问者模式原理

1.2.1 访问者模式类图

1.2.2 模式角色说明

访问者模式包含以下主要角色:

  • 抽象访问者(Visitor)角色:可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用。

  • 具体访问者(ConcreteVisitor)角色:访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法。

  • 抽象元素(Element)角色:被访问的数据元素接口,定义了一个接受访问者的方法( accept ),其意义是指,每一个元素都要可以被访问者访问。

  • 具体元素(ConcreteElement)角色: 具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回。

  • 对象结构(Object Structure)角色:包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构。

  • 客户端 ( Client ) : 使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象。

二、访问者模式的应用

2.1 需求说明

我们以超市购物为例,假设超市中的三类商品: 水果,糖果,酒水进行售卖。我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计。我们先来定义糖果类和酒类、水果类。

2.2 需求实现

2.2.1 V1版本

2.2.1.1 抽象产品类
package main.java.cn.test.visitor.V1;import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 15:52:37* @description 抽象商品父类*/
public abstract class Product {//商品名private String name;// 生产日期private LocalDate producedDate;//单品价格private double price;public Product(String name, LocalDate producedDate,double price) {this.name = name;this.producedDate = producedDate;this.price = price;}public String getName() {return name;}public void setName(String name) {this.name = name;}public LocalDate getProducedDate() {return producedDate;}public void setProducedDate(LocalDate producedDate) {this.producedDate = producedDate;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}
}
2.2.1.2 糖果类
package main.java.cn.test.visitor.V1;import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 15:53:58* @description 糖果类*/
public class Candy extends Product {public Candy(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}
}
2.2.1.3 酒水类
package main.java.cn.test.visitor.V1;import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 15:54:37* @description 酒水类*/
public class Wine extends Product {public Wine(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}
}
2.2.1.4 水果类
package main.java.cn.test.visitor.V1;import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 15:55:05* @description 水果类*/
public class Fruit extends Product {//重量private float weight;public Fruit(String name, LocalDate producedDate, double price, float weight) {super(name, producedDate, price);this.weight = weight;}public float getWeight() {return weight;}public void setWeight(float weight) {this.weight = weight;}}
2.2.1.5 访问者接口
package main.java.cn.test.visitor.V1;/*** @author ningzhaosheng* @date 2024/1/15 15:58:57* @description 访问者接口-根据入参不同调用对应的重载方法*/
public interface Visitor {//糖果重载方法public void visit(Candy candy);//酒类重载方法public void visit(Wine wine);//水果重载方法public void visit(Fruit fruit);}
2.2.1.6 访问者实现类
package main.java.cn.test.visitor.V1;import java.text.NumberFormat;
import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 15:59:38* @description 折扣计价访问者类*/
public class DiscountVisitor implements Visitor {private LocalDate billDate;public DiscountVisitor(LocalDate billDate) {this.billDate = billDate;System.out.println("结算日期: " + billDate);}@Overridepublic void visit(Candy candy) {System.out.println("糖果: " + candy.getName());System.out.println("生产日期:" + candy.getProducedDate());//获取产品生产天数long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();if (days > 180) {System.out.println("超过半年的糖果,请勿食用!");} else {double rate = 0.9;double discountPrice = candy.getPrice() * rate;System.out.println("糖果打折后的价格" + NumberFormat.getCurrencyInstance().format(discountPrice));}}@Overridepublic void visit(Wine wine) {System.out.println("酒类: " + wine.getName() + ",无折扣价格!");System.out.println("原价: " + NumberFormat.getCurrencyInstance().format(wine.getPrice()));}@Overridepublic void visit(Fruit fruit) {String message = null;System.out.println("水果: " + fruit.getName());System.out.println("生产日期:" + fruit.getProducedDate());//获取产品生产天数long days = billDate.toEpochDay() -fruit.getProducedDate().toEpochDay();double rate = 0;if (days > 7) {message = "超过七天的水果,请勿食用!";} else if (days > 3) {rate = 0.5;message = "水果超打折后的价格";} else {rate = 1;message = "水果价格: ";}double discountPrice = fruit.getPrice() * fruit.getWeight() * rate;System.out.println(message + NumberFormat.getCurrencyInstance().format(discountPrice));}
}
2.2.1.7 测试类
package main.java.cn.test.visitor.V1;import java.text.NumberFormat;
import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 16:02:09* @description 测试类*/
public class Test {public static void main(String[] args) {//德芙巧克力,生产日期2023-10-1 ,原价 10元Candy candy = new Candy("德芙巧克力", LocalDate.of(2023, 10, 1), 10.0);Visitor visitor = new DiscountVisitor(LocalDate.of(2024, 1, 11));visitor.visit(candy);System.out.println("====================");// 徐福记,生产日期2022年-10-1,原价10元Candy candy1 = new Candy("徐福记", LocalDate.of(2022, 10, 1), 10.0);Visitor visitor1 = new DiscountVisitor(LocalDate.of(2024, 1, 11));visitor1.visit(candy1);System.out.println("====================");// 茅台酒,生产日期2022年-10-1,原价5000元Wine wine = new Wine("茅台原浆酒",LocalDate.of(2022,10,1),5000);Visitor visitor2 = new DiscountVisitor(LocalDate.of(2024, 1, 11));visitor2.visit(wine);System.out.println("====================");// 橘子,生产日期2024年-1-10,原价 8元,买3斤Fruit fruit = new Fruit("广西小沙糖桔",LocalDate.of(2024,1,10),8.00,3);Visitor visitor3 = new DiscountVisitor(LocalDate.of(2024, 1, 15));visitor3.visit(fruit);}}

上面的代码虽然可以完成当前的需求,但是设想一下这样一个场景: 由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题)。

2.2.2 V2版本

首先我们定义一个接待访问者的类 Acceptable,其中定义了一个accept(Visitorvisitor)方法,只要是visitor的子类都可以接收。

2.2.2.1 接待者接口
package main.java.cn.test.visitor.V2;/*** @author ningzhaosheng* @date 2024/1/15 16:33:22* @description 接待者接口(抽象元素角色)*/
public interface Acceptable {//接收所有的Visitor访问者的子类实现类public void accept(Visitor visitor);
}
2.2.2.2 糖果类(优化)
package main.java.cn.test.visitor.V2;import main.java.cn.test.visitor.V1.Product;import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 16:35:22* @description 糖果类*/
public class Candy extends Product implements Acceptable {public Candy(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}@Overridepublic void accept(Visitor visitor) {//accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型visitor.visit(this);}
}
2.2.2.3 酒水类(优化)
package main.java.cn.test.visitor.V2;import main.java.cn.test.visitor.V1.Product;import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 15:54:37* @description 酒水类*/
public class Wine extends Product implements Acceptable{public Wine(String name, LocalDate producedDate, double price) {super(name, producedDate, price);}@Overridepublic void accept(Visitor visitor) {//accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型visitor.visit(this);}
}
2.2.2.4 水果类(优化)
package main.java.cn.test.visitor.V2;import main.java.cn.test.visitor.V1.Product;import java.time.LocalDate;/*** @author ningzhaosheng* @date 2024/1/15 15:55:05* @description 水果类*/
public class Fruit extends Product implements Acceptable {//重量private float weight;public Fruit(String name, LocalDate producedDate, double price, float weight) {super(name, producedDate, price);this.weight = weight;}public float getWeight() {return weight;}public void setWeight(float weight) {this.weight = weight;}@Overridepublic void accept(Visitor visitor) {//accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型visitor.visit(this);}
}
2.2.2.5 测试类
package main.java.cn.test.visitor.V2;import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;/*** @author ningzhaosheng* @date 2024/1/15 16:43:32* @description 测试类*/
public class Test {public static void main(String[] args) {//模拟添加多个商品的操作List<Acceptable> products = Arrays.asList(new Candy("德芙巧克力", LocalDate.of(2023, 10, 1), 10.0),new Candy("徐福记", LocalDate.of(2022, 10, 1), 10.0),new Wine("茅台原浆酒", LocalDate.of(2022, 10, 1), 5000),new Fruit("广西小沙糖桔", LocalDate.of(2024, 1, 10), 8.00, 3));Visitor visitor = new DiscountVisitor(LocalDate.of(2024, 1, 15));for (Acceptable product : products) {product.accept(visitor);}}}

代码编写到此处,就可以应对计价方式或者业务逻辑的变化了,访问者模式成功地将数据资源(需实现接待者接口)与数据算法 (需实现访问者接口)分离开来。重载方法的使用让多样化的算法自成体系,多态化的访问者接口保证了系统算法的可扩展性,数据则保持相对固定,最终形成⼀个算法类对应⼀套数据。

三、访问者模式总结

3.1 访问者模式的优点

  • 扩展性好

在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好

通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  • 分离无关行为

通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

3.2 访问者模式的缺点

  • 对象结构变化很困难

在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则

访问者模式依赖了具体类,而没有依赖抽象类。

3.3 访问者模式的使用场景

  • 当对象的数据结构相对稳定,而操作却经常变化的时候。

比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。

  • 需要将数据结构与不常用的操作进行分离的时候。

比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。

  • 需要在运行时动态决定使用哪些对象和方法的时候。

比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。

好了,本次分享就到这里,欢迎大家继续阅读《设计模式》专栏其他设计模式内容,如果有帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/633453.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

随笔03 笔记整理

图源&#xff1a;文心一言 关于我的考研与信息安全类博文整理~&#x1f95d;&#x1f95d; 第1版&#xff1a;整理考研类博文~&#x1f9e9;&#x1f9e9; 第2版&#xff1a;提前列出博文链接&#xff0c;以便小伙伴查阅~&#x1f9e9;&#x1f9e9; 第3版&#xff1a;整理We…

上海亚商投顾:沪指探底回升 大金融板块午后走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 指昨日探底回升&#xff0c;深成指、创业板指午后跌超1%&#xff0c;尾盘集体拉升翻红&#xff0c;北证50指数涨…

一万六千字大章:Chrome 浏览器插件 V3 版本 Manifest.json 文件全字段解析

Chrome 浏览器插件 V3 版本 Manifest.json 文件全字段解析 Manifest.json 文件格式 每个扩展程序的根目录中都必须有一个 manifest.json 文件&#xff0c;其中列出了有关该扩展程序的结构和行为的重要信息。 1、Demo 展示 1. 最小文件 {"manifest_version": 3,&quo…

2024哪些跨境电商平台值得做?

时代的巨变在2023年尤其明显&#xff0c;这一年随着全球化进程的加深&#xff0c;跨境出海处于“高景气”阶段。为了在跨境出海浪潮中保有稳定的地位甚至获得增长&#xff0c;跨境人最需要关注的是哪个跨境电商平台成为大势&#xff0c;用户所选择的平台是什么&#xff1f;在跨…

echarts图表

所谓图表就是用来统计一些数据的&#xff0c;图表有很多种&#xff0c;有折线图、柱状图、饼状图、散点图等等多种多样的样式&#xff0c;我们可以根据自身需求来选择。 我们在用的时候是要先下载的&#xff0c;我们可以直接搜echarts官网&#xff0c;里面有快速入门&#xff…

select...in在mybatis里使用(巨坑!!)

情景&#xff1a;最近遇到了一个bug&#xff1a; 在DAO层里的这个sql语句&#xff0c;传入的参数没问题&#xff0c;在mysql里面查询也查询到了数据&#xff0c;为什么在dao层执行的时候查到数据不完整甚至没有呢&#xff1f; 主要原因&#xff1a; Mybatis 在 处理#{}时&…

STM32 基本定时器反转LED

引脚是什么为什么要初始化引脚&#xff1f; 在嵌入式系统中&#xff0c;引脚是微控制器或微处理器上的物理引脚&#xff0c;用于连接外部设备、传感器或其他芯片。每个引脚都有特定的功能和用途&#xff0c;例如输入、输出、模拟输入、电源供应等。STM32F103C8T6引脚图&#xf…

2018年认证杯SPSSPRO杯数学建模B题(第二阶段)动态模糊图像全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 动态模糊图像复原 B题 动态模糊图像 原题再现&#xff1a; 人眼由于存在视觉暂留效应&#xff0c;所以看运动的物体时&#xff0c;看到的每一帧画面都包含了一段时间内 (大约 1/24 秒) 的运动过程&#xff0c;所以这帧画面事实上是模糊的。对…

C++入门学习(一)写一个helloworld

1、头文件 #include <iostream> using namespace std; 任何程序都需要这两句的&#xff0c;写上就好。 2、主文件 int main() {cout<<"Hello World!"<<endl;return 0; } 由于是int型数据&#xff0c;所以要返回一个值&#xff0c;即return0。…

JVM:垃圾回收机制(GC)

垃圾判断&#xff1a; 引用计数算法&#xff1a; 在对象中添加一个引用计数器&#xff0c;当每有一个地方引用它时&#xff0c;计数器值加一。当引用失效时&#xff0c;计数器值就减一。当一个对象的计数器为零时&#xff0c;表示该对象没有被任何其他对象引用&#xff0c;因此…

【动态规划】【数学】【C++算法】18赛车

作者推荐 视频算法专题 本文涉及知识点 动态规划 数学 LeetCode818赛车 你的赛车可以从位置 0 开始&#xff0c;并且速度为 1 &#xff0c;在一条无限长的数轴上行驶。赛车也可以向负方向行驶。赛车可以按照由加速指令 ‘A’ 和倒车指令 ‘R’ 组成的指令序列自动行驶。 当…

mycat实现mysql读写分离

一. mycat集群HaproxyKeepalived mycat集群HaproxyKeepalivedmysql1主2从 环境规划 centos7.9 1主2从&#xff0c;读写分离 名称ip端口mysql-master192.168.1.2203306mysql-slave1192.168.1.2213306mysql-slave2192.168.1.2223306mycat-1192.168.1.2218066mycat-2192.168.1.…

Redis集群搭建

为什么要有集群 之前我们已经讲了主从的概念&#xff0c;一主可以多从&#xff0c;如果同时的访问量过大(1000w),主服务肯定就会挂掉&#xff0c;数据服务就挂掉了或者发生自然灾难 大公司都会有很多的服务器(华东地区、华南地区、华中地区、华北地区、西北地区、西南地区、东…

Android Studio读写低频RFID T5557卡源码

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?id675212889085&spma1z10.5-c.w4002-21818769070.13.21166f89nKgnJ7 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xml…

【Docker】实战多阶段构建 Laravel 镜像

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; 本节适用于 PHP 开发者阅读。Laravel 基于 8.x 版本&#xff0c;各个版本的文件结构可能会有差异&#xff0c;请根据实际自行修改。 准备 新…

webpack面试题学习

说说你对webpack的理解&#xff1f;解决了什么问题&#xff1f; 说说webpack的构建流程? 说说webpack中常见的Loader&#xff1f;解决了什么问题&#xff1f; 说说webpack中常见的Plugin&#xff1f;解决了什么问题&#xff1f; 说说Loader和Plugin的区别&#xff1f;编写Load…

leetcode 013二维区域和检索---矩阵不可变

给定一个二维矩阵 matrix&#xff0c;以下类型的多个请求&#xff1a; 计算其子矩形范围内元素的总和&#xff0c;该子矩阵的左上角为 (row1, col1) &#xff0c;右下角为 (row2, col2) 。 实现 NumMatrix 类&#xff1a; NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进…

Failed to connect to github.com

链接github超时 hosts文件夹下加上

Python使用HTTP代理实现网络请求的自动化

随着网络技术的发展&#xff0c;网络请求成为了许多应用的重要组成部分。然而&#xff0c;手动发送网络请求不仅效率低下&#xff0c;而且容易出错。为了解决这个问题&#xff0c;我们可以使用Python来实现网络请求的自动化。而HTTP代理可以帮助我们更好地控制和管理这些请求。…

【小笔记】时序数据分类算法最新小结

2024.1.15 最近基于时序数据训练分类算法&#xff0c;对其进行了一番了解&#xff0c;主要围绕以下几点&#xff1a; 时序数据算法有哪些细分类&#xff1f;时序数据分类算法经典模型&#xff1f;当下时序分类算法模型强baseline&#xff1f;有没有现成的工具&#xff1f; 1…