C#设计模式之访问者模式
总目录
前言
在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为,如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?这就要使用到本文的访问者模式了!
1 基础介绍
- 定义: 表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。
- 将数据操作与数据结构分离,使得同一组操作可以作用于不同的数据结构。
- 访问者模式是用来封装一些施加于某种数据结构之上的操作。它使得可以在不改变元素本身的前提下增加作用于这些元素的新操作,访问者模式的目的是把操作从数据结构中分离出来。
- 解决在稳定数据结构和易变操作之间的耦合问题,使得操作可以独立于数据结构变化。
- 访问者模式中的角色
- 抽象访问者角色(Vistor): 声明一个包括多个访问操作,多个操作针对多个具体节点角色(可以说有多少个具体节点角色就有多少访问操作),使得所有具体访问者必须实现的接口。
- 具体访问者角色(ConcreteVistor):实现抽象访问者角色中所有声明的接口,也可以说是实现对每个具体节点角色的新的操作。
- 抽象节点角色(Element):声明一个接受操作,接受一个访问者对象作为参数,如果有其他参数,可以在这个“接受操作”里在定义相关的参数。
- 具体节点角色(ConcreteElement):实现抽象元素所规定的接受操作。
- 结构对象角色(ObjectStructure):节点的容器,可以包含多个不同类或接口的容器。
2 使用场景
当需要对一个对象结构中的对象执行多种不同的且不相关的操作时,尤其是这些操作需要避免"污染"对象类本身。
- 如果系统有比较稳定的数据结构,而又有易于变化的算法时,此时可以考虑使用访问者模式。因为访问者模式使得算法操作的添加比较容易。
- 如果一组类中,存在着相似的操作,为了避免出现大量重复的代码,可以考虑把重复的操作封装到访问者中。
- 如果一个对象存在着一些与本身对象不相干,或关系比较弱的操作时,为了避免操作污染这个对象,则可以考虑把这些操作封装到访问者对象中。
3 实现方式
1. 实现方式 - 无ObjectStructure角色
实例:为不同形状计算面积和周长。假设我们有不同的几何形状类,比如圆形和矩形。我们希望计算它们的面积和周长,而不改变这些形状类的实现。
定义抽象访问者,声明可以作用于不同形状的操作。
// 访问者接口,声明访问不同元素的方法
public interface IVisitor
{// 访问圆形的操作void Visit(Circle circle);// 访问矩形的操作void Visit(Rectangle rectangle);
}
定义具体访问者,实现两个具体的访问者,一个用于计算形状的面积,另一个用于计算形状的周长。
// 具体访问者:用于计算面积
public class AreaVisitor : IVisitor
{public void Visit(Circle circle){double area = Math.PI * circle.Radius * circle.Radius;Console.WriteLine($"圆的面积: {area}");}public void Visit(Rectangle rectangle){double area = rectangle.Length * rectangle.Width;Console.WriteLine($"矩形的面积: {area}");}
}// 具体访问者:用于计算周长
public class PerimeterVisitor : IVisitor
{public void Visit(Circle circle){double perimeter = 2 * Math.PI * circle.Radius;Console.WriteLine($"圆的周长: {perimeter}");}public void Visit(Rectangle rectangle){double perimeter = 2 * (rectangle.Length + rectangle.Width);Console.WriteLine($"矩形的周长: {perimeter}");}
}
定义抽象元素/节点,声明接受访问者的方法。所有几何形状类都将实现这个接口。
// 元素接口,定义接受访问者的方法
public interface IShape
{// 接受访问者void Accept(IVisitor visitor);
}
定义具体元素/节点,实现具体的形状类,比如圆形和矩形。这些类都将实现Accept方法,允许访问者访问自己。
// 圆形类,表示一个具体的形状
public class Circle : IShape
{public double Radius { get; }public Circle(double radius){Radius = radius;}// 接受访问者,调用访问者的Visit方法public void Accept(IVisitor visitor){visitor.Visit(this);}
}// 矩形类,表示一个具体的形状
public class Rectangle : IShape
{public double Length { get; }public double Width { get; }public Rectangle(double length, double width){Length = length;Width = width;}// 接受访问者,调用访问者的Visit方法public void Accept(IVisitor visitor){visitor.Visit(this);}
}
客户端使用
class Program
{static void Main(string[] args){// 创建形状对象IShape circle = new Circle(5);IShape rectangle = new Rectangle(4, 6);// 创建访问者对象IVisitor areaVisitor = new AreaVisitor();IVisitor perimeterVisitor = new PerimeterVisitor();// 计算圆形的面积和周长circle.Accept(areaVisitor);circle.Accept(perimeterVisitor);// 计算矩形的面积和周长rectangle.Accept(areaVisitor);rectangle.Accept(perimeterVisitor);}
}
2. 实现方式 - 有ObjectStructure角色
案例:医院看病后,医生开药单,拿着药单先去缴费,然后去取药的过程
/// <summary>/// 抽象访问者/// </summary>public abstract class Visitor{protected string name { get; set; }public Visitor(string name){this.name = name;}public abstract void Visit(MedicineA a);public abstract void Visit(MedicineB b);}
/// <summary>/// 具体访问者:划价员/// </summary>public class Charger :Visitor{public Charger(string name) : base(name) { }public override void Visit(MedicineA a){Console.WriteLine("划价员:"+this.name+"给药"+a.GetName()+"价格:"+a.GetPrice());}public override void Visit(MedicineB b){Console.WriteLine("划价员:" + this.name + "给药" + b.GetName() + "价格:" + b.GetPrice());}}
/// <summary>/// 具体访问者:药房工作者/// </summary>public class WorkerOfPharmacy:Visitor{public WorkerOfPharmacy(string name) : base(name) { }public override void Visit(MedicineA a){Console.WriteLine("药房工作者:"+this.name+",拿药:"+a.GetName());}public override void Visit(MedicineB b){Console.WriteLine("药房工作者:" + this.name + ",拿药:" + b.GetName());}}
/// <summary>/// 抽象元素:药/// </summary>public abstract class Medicine{protected string name { get; set; }protected double price { get; set; }public Medicine(string name, double price){this.name = name;this.price = price;}public string GetName(){return name;}public double GetPrice(){return price;}public void SetPrice(double price){this.price = price;}public abstract void accept(Visitor visitor);}
/// <summary>/// 具体元素:A名称药/// </summary>public class MedicineA:Medicine{public MedicineA(string name, double price) : base(name, price) { }public override void accept(Visitor visitor){visitor.visitor(this);}}
/// <summary>/// 具体元素:B名称药/// </summary>public class MedicineB:Medicine{public MedicineB(string name, double price) : base(name, price) { }public override void accept(Visitor visitor){visitor.visitor(this);}}
/// <summary>/// 具体元素:药单/// </summary>public class Presciption{private List<Medicine> listmedicine = new List<Medicine>();public void accpet(Visitor visitor){foreach (var item in listmedicine){item.accept(visitor);}}public void add(Medicine med){listmedicine.Add(med);}public void remove(Medicine med){listmedicine.Remove(med);}}
/// <summary>/// C#设计模式-访问者模式/// </summary>class Program{static void Main(string[] args){//药类型Medicine a = new MedicineA("药A", 10);MedicineB b = new MedicineB("药B", 20);//药单Presciption presciption = new Presciption();presciption.add(a);presciption.add(b);Visitor charger = new Charger("张三"); //划价员Visitor workerOfPharmacy = new WorkerOfPharmacy("李四"); //抓药员presciption.accpet(charger); //划价Console.WriteLine();presciption.accpet(workerOfPharmacy); //抓药}}
4 优缺点分析
设计模式本质就是针对编码过程中常见问题的一种解决方案,通俗讲就是一种堵漏洞的方式,但是没有一种设计模式能够堵完所有的漏洞,即使是组合各种设计模式也是一样。每个设计模式都有漏洞,都有它们解决不了的情况或者变化。每一种设计模式都假定了某种变化,也假定了某种不变化。Visitor模式假定的就是操作变化,而Element类层次结构稳定。
优点:
- 访问者模式使得添加新的操作变得容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,添加新的操作会变得很复杂。而使用访问者模式,增加新的操作就意味着添加一个新的访问者类。因此,使得添加新的操作变得容易。
- 访问者模式使得有关的行为操作集中到一个访问者对象中,而不是分散到一个个的元素类中。这点类似与”中介者模式”。
- 访问者模式可以访问属于不同的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。
缺点:
- 增加新的元素类变得困难。每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中添加相应的具体操作。具体来说,Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。
结语
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
【C#设计模式-访问者模式】
深入探索 C# 中的访问者模式:让对象结构变得更加灵活
使用 C# 实现23种常见的设计模式
C#设计模式之二十一访问者模式(Visitor Pattern)【行为型】
C#设计模式(22)——访问者模式(Vistor Pattern)
C#设计模式系列:访问者模式(Visitor)
相关文章:
C#设计模式之访问者模式
总目录 前言 在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为,如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。如何在不更改类层次结构的前提下,…...

一次RPC调用过程是怎么样的?
注册中心 RPC(Remote Procedure Call)翻译成中文就是 {远程过程调用}。RPC 框架起到的作用就是为了实现,调用远程方法时,能够做到和调用本地方法一样,让开发人员更专注于业务开发,不用去考虑网络编程等细节…...

鸭脖变“刺客”,啃不起了
撰文|ANGELICA 编辑|ANGELICA 审核|烨 Lydia 声明|图片来源网络。日晞研究所原创文章,如需转载请留言申请开白。 你有多久没吃卤味了? 2020年之后,人们对于几大卤味巨头的关注度正在下降。 …...
力扣 —— 删除有序数组中的重复项
题目思路 两个指针,一个是游标的功能,负责遍历整个数组,一个是定位器的功能,如果有相等的则表示定位器目前指向的元素是重复的,定位器不动,等待游标往下找到不重复的数填充进来,因为游标会遍历…...
rmdir :删除空文件夹
一、命令简介 在 Linux 系统中,rmdir 命令用于删除空目录(文件夹)。 二、命令参数 rmdir 目录 三、命令示例 删除名为 dir1 的空目录: rmdir dir1删除多个空目录: rmdir dir1 dir2 dir3注意事项…...

网络爬虫Request静态页面数据获取
在现代 Web 开发中,HTTP 请求(Request)是与服务器进行通信的核心操作。无论是在前端还是后端开发中,数据的获取、传递以及处理都离不开请求的应用。特别是在静态页面的数据获取中,使用请求可以将页面变得更加动态和互动,从而大大提升用户体验,使得页面内容更加丰富和灵活…...

网页聊天——测试报告——Selenium自动化测试
一,项目概括 1.1 项目名称 网页聊天 1.2 测试时间 2024.9 1.3 编写目的 对编写的网页聊天项目进行软件测试活动,揭示潜在问题,总结测试经验 二,测试计划 2.1 测试环境与配置 服务器:云服务器 ubuntu_22 PC机&am…...
mysql5.7常用操作命令手册
文章目录 前言一、关闭mysql服务1.mha节点,关闭MHA高可用2.主节点,摘掉vip,停掉mysql服务3.从节点,停掉mysql服务 二、启动mysql1.启动数据库顺序2.主节点,登陆数据库检查主库状态,将主库改成读写状态3.从节点启动配置数据库&…...

前端组件库Element UI 的使用
一、准备工作 1.确保安装了开发软件 VS Code(此处可查阅安装 VS Code教程),确保相关插件安装成功 2.安装Node.js 和创建Vue项目(此处可查阅安装创建教程) 3.成功在VS Code运行一个Vue项目(此处可查阅运行…...

【C++ 基础数学 】2121. 2615相同元素的间隔之和|1760
本文涉及的基础知识点 基础数学 LeetCode2121. 相同元素的间隔之和 难度分:1760 令2165,和此题几乎相等。 给你一个下标从 0 开始、由 n 个整数组成的数组 arr 。 arr 中两个元素的 间隔 定义为它们下标之间的 绝对差 。更正式地,arr[i] 和…...

从手动测试菜鸟,到自动化测试老司机,实现自动化落地
虽然许多伙伴是一个测试老人了,但是基本上所有的测试经验都停留在手工测试方面,对于自动化测试方面的实战经验少之又少。 其实,究其原因:一方面是,自动化方面不求上进,觉得会手工测试就可以了,自…...
docker zookeeper集群启动报错:Cannot open channel to * at election address /ip:3888
下面几点需要注意的: 1、确认在每个$zookeeper_home/data/myid中有对应数字 2、是否关闭防火墙:systemctl stop firewalld,systemctl disable firewalld 3、zoo.cfg中的server需要写成以下形式的: 假如有两台机器,1…...

【Linux探索学习】第一弹——Linux的基本指令(上)——开启Linux学习第一篇
前言: 在进入Linux学习之前,我们首先要先做好以下两点:1、已经基本掌握C语言或C,2、已经配置好了Linux的环境,做完以上两点后我们就开始Linux的学习,今天我们首先要学习的就是Linux中最基础的操作ÿ…...
3.Vue2结合element-ui实现国际化多语言i18n
1.安装vue-i18n npm install vue-i18n8.2.1说明:Vue2使用vue-i18n是8.x,Vue3使用的版本是9.x以上,使用错了会导致报错 2.创建多语言文件 在src/下创建src/lang/langs/zh.js和src/lang/langs/en.js两个文件,里面内容如下&#x…...

整数二分算法和浮点数二分算法
整数二分算法和浮点数二分算法 二分 现实中运用到二分的就是猜数字的游戏 假如有A同学说B同学所说数的大小,B同学要在1~100中间猜中数字65,当B同学每次说的数都是范围的一半时这就算是一个二分查找的过程 二分查找的前提是这个数字序列要有单调性 基…...

智能回收箱的功能和使用步骤介绍
智能回收箱是现代城市环保与资源循环利用领域的一项创新技术,它通过集成各种智能化功能,提高了垃圾回收的效率和准确性,促进了垃圾分类与减量。随着全球对环境保护意识的增强和智慧城市概念的推广,智能回收箱的发展前景非常广阔&a…...

Remix在SPA模式下,出现ErrorBoundary错误页加载Ant Design组件报错,不能加载样式的问题
Remix是一个既能做服务端渲染,又能做单页应用的框架,如果想做单页应用,又想学服务端渲染,使用Remix可以降低学习成本。最近,在学习Remix的过程中,遇到了在SPA模式下与Ant Design整合的问题。 我用Remix官网…...

ADB ROOT开启流程
开启adb root 选项后,执行如下代码: packages/apps/Settings/src/com/android/settings/development/AdbRootPreferenceController.java mADBRootService new ADBRootService(); Override public boolean onPreferenceChange(Preference preference…...

传输层协议 —— TCP协议(上篇)
目录 1.认识TCP 2.TCP协议段格式 3.可靠性保证的机制 确认应答机制 超时重传机制 连接管理机制 三次握手 四次挥手 1.认识TCP 在网络通信模型中,传输层有两个经典的协议,分别是UDP协议和TCP协议。其中TCP协议全称为传输控制协议(Tra…...

YOLOv8改进,YOLOv8的Neck替换成AFPN(CVPR 2023)
摘要 多尺度特征在物体检测任务中对编码具有尺度变化的物体非常重要。多尺度特征提取的常见策略是采用经典的自上而下和自下而上的特征金字塔网络。然而,这些方法存在特征信息丢失或退化的问题,影响了非相邻层次的融合效果。一种渐进式特征金字塔网络(AFPN),以支持非相邻…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...

CTF show 数学不及格
拿到题目先查一下壳,看一下信息 发现是一个ELF文件,64位的 用IDA Pro 64 打开这个文件 然后点击F5进行伪代码转换 可以看到有五个if判断,第一个argc ! 5这个判断并没有起太大作用,主要是下面四个if判断 根据题目…...
背包问题双雄:01 背包与完全背包详解(Java 实现)
一、背包问题概述 背包问题是动态规划领域的经典问题,其核心在于如何在有限容量的背包中选择物品,使得总价值最大化。根据物品选择规则的不同,主要分为两类: 01 背包:每件物品最多选 1 次(选或不选&#…...