C#设计模式之访问者模式

server/2024/9/25 1:51:09/

总目录


前言

在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为,如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?这就要使用到本文的访问者模式了!


1 基础介绍

  1. 定义: 表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。
  2. 将数据操作与数据结构分离,使得同一组操作可以作用于不同的数据结构。
  3. 访问者模式是用来封装一些施加于某种数据结构之上的操作。它使得可以在不改变元素本身的前提下增加作用于这些元素的新操作,访问者模式的目的是把操作从数据结构中分离出来
  4. 解决在稳定数据结构和易变操作之间的耦合问题,使得操作可以独立于数据结构变化。
  5. 访问者模式中的角色
    • 抽象访问者角色(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)


http://www.ppmy.cn/server/121593.html

相关文章

数据脱敏-快速使用

1.数据脱敏定义 数据脱敏百度百科中是这样定义的&#xff1a; 数据脱敏&#xff0c;指对某些敏感信息通过脱敏规则进行数据的变形&#xff0c;实现敏感隐私数据的可靠保护。 因为在真正的生产环境中,很多数据是不能直接返回,但是我们工作的时候可能经常性的需要返回一些用户信…

LabVIEW提高开发效率技巧----VI服务器和动态调用

VI服务器&#xff08;VI Server&#xff09;和动态调用是LabVIEW中的两个重要功能&#xff0c;可以有效提升程序的灵活性、模块化和可扩展性。通过这两者的结合&#xff0c;开发者可以在运行时动态加载和调用VI&#xff08;虚拟仪器&#xff09;&#xff0c;实现更为复杂的应用…

【速成Redis】04 Redis 概念扫盲:事务、持久化、主从复制、哨兵模式

前言&#xff1a; 前三篇如下&#xff1a; 【速成Redis】01 Redis简介及windows上如何安装redis-CSDN博客 【速成Redis】02 Redis 五大基本数据类型常用命令-CSDN博客 【速成Redis】03 Redis 五大高级数据结构介绍及其常用命令 | 消息队列、地理空间、HyperLogLog、BitMap、…

项目实战 (15)--- 代码区块重构及相关技术落地

目录 背景 思想与技术方案 概述 路由及socket 封装模式 技术描述 方案 1 方案2 各类连接资源管理封装 vector db connection cache management web socket 管理 service 封装 代码实现 service 层 router层 resource层 小结 背景 到目前为止,视频搜索系统功…

惊艳到你的算法

场景一 小时候去商场玩时&#xff0c;爸爸妈妈经常告诉我&#xff0c;如果你在商场走丢了&#xff0c;千万不要到处乱跑&#xff0c;站在原地不动就好了&#xff0c;我们会来找你的。 长大后学了计算机才明白&#xff0c;原来那个时候爸爸妈妈就已经会用DFS算法。如果我在原地…

招联金融2025秋招--大量招后台、算法

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策划 产品运营…

前后端数据交互 笔记03(get和post方法)

1.解决页面网站中&#xff0c;中文出现乱码的情况&#xff1a; request.setCharacterEncoding("utf-8") response.setCharaterEncoding("utf-8") 2.给后端设置返回json数据&#xff1a; response.setContentType("text/json,charsetutf-8") …

Vue 项目实战4-无缝轮播图

养成好习惯&#xff0c;先赞后看&#xff0c;感谢对作者大大的支持 一、话不多说&#xff0c;直接上效果图&#xff1a; 完整视频展示链接如下&#xff1a; https://item.taobao.com/item.htm?ftt&id833405684191 二、实现思路 HTML结构 文档头部设置&#xff1a;定义…