设计模式-访问者(Visitor)模式详解和应用

news2024/11/16 12:42:07

文章目录

  • 前言
  • 访问者模式介绍
  • 结构
  • 包含的角色
  • 应用场景
  • 代码示例
  • 访问者模式的扩展
  • 访问者模式优缺点
  • 总结


前言

最近在做一个根据数学表达式生成java执行代码的功能,其中用到了访问者模式。使我对访问者模式有了更深入的理解。故写下此篇文章分享出来,不足之处请大家指正。


访问者模式介绍

访问者模式(Visitor Pattern)是GoF提出的23种设计模式中的一种,属于行为模式。GoF《Design Pattern》中的定义 :表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

结构

在这里插入图片描述

包含的角色

  • 抽象访问者(Visitor)。定义了访问者可以访问的元素对象的接口,包含多个 visit() 方法,每个方法对应一个具体元素对象。
  • 具体访问者(ConcreteVisitor)。实现了抽象访问者接口,对不同类型的元素对象进行具体的操作。
  • 抽象元素(Element)。定义了接受访问者accept方法,不同的具体元素可以实现该方法以不同的方式响应访问者的访问。
  • 具体元素(ConcreteElement)。实现了抽象元素中定义的接受访问者访问的方法,并在其中调用访问者的访问方法。
  • 对象结构(Object Structure)。定义了具体元素的集合,并提供了遍历集合中元素的方法。

应用场景

  1. 电商网站的商品分类与操作

    • 电商网站通常有大量的商品,这些商品可以按照不同的属性进行分类,如数码产品、服装鞋帽、家居用品等。
    • 使用访问者模式,可以定义一个访问者对象,它能够访问不同类型的商品对象,并根据需要执行不同的操作,如按照价格排序、根据品牌筛选等。
  2. 图形编辑器的操作处理

    • 在一个复杂的图形编辑器中,有多种图形元素,如线条、圆形、矩形等。
    • 访问者模式允许定义一个访问者对象,它能够访问这些图形元素并执行不同的操作,如移动、缩放、旋转或改变颜色等。
  3. 编译器和解释器设计

    • 在编译器或解释器的设计中,抽象语法树(AST)是一个常见的数据结构,它包含了程序的各个部分。
    • 使用访问者模式,可以为AST中的不同节点类型定义不同的访问者,以执行如类型检查、代码优化、代码生成等操作。

代码示例

将Visitor设计模式与Shape类结合使用,你可以定义一个Shape接口和多个具体的Shape实现类(如Circle、Rectangle、Triangle等),并为这些Shape对象实现不同的操作。

以下是一个简单的示例,展示了如何使用Visitor设计模式对Shape对象进行操作:

// Shape 接口
interface Shape {  
    void accept(ShapeOperation operation);  
}  
  
// Circle 类,实现了 Shape 接口  
class Circle implements Shape {  
    private double radius;  
  
    public Circle(double radius) {  
        this.radius = radius;  
    }  
  
    public double getRadius() {  
        return radius;  
    }  
  
    @Override  
    public void accept(ShapeOperation operation) {  
        operation.visit(this);  
    }  
}  
  
// Square 类,实现了 Shape 接口  
class Square implements Shape {  
    private double side;  
  
    public Square(double side) {  
        this.side = side;  
    }  
  
    public double getSide() {  
        return side;  
    }  
  
    @Override  
    public void accept(ShapeOperation operation) {  
        operation.visit(this);  
    }  
}

定义一个ShapeVisitor接口和具体的Visitor实现类:


// ShapeVisitor 接口
interface ShapeVisitor {  
    void visit(Circle circle);  
    void visit(Square square);  
}  
  
// PrintAreaVisitor 类,实现了 ShapeOperation 接口  
class PrintAreaVisitor implements ShapeVisitor {  
    @Override  
    public void visit(Circle circle) {  
        double area = Math.PI * circle.getRadius() * circle.getRadius();  
        System.out.println("Circle area: " + area);  
    }  
  
    @Override  
    public void visit(Square square) {  
        double area = square.getSide() * square.getSide();  
        System.out.println("Square area: " + area);  
    }  
}  
  
// PrintPerimeterVisitor 类,实现了 ShapeOperation 接口  
class PrintPerimeterVisitor implements ShapeVisitor {  
    @Override  
    public void visit(Circle circle) {  
        double perimeter = 2 * Math.PI * circle.getRadius();  
        System.out.println("Circle perimeter: " + perimeter);  
    }  
  
    @Override  
    public void visit(Square square) {  
        double perimeter = 4 * square.getSide();  
        System.out.println("Square perimeter: " + perimeter);  
    }  
}

使用Visitor对Shape对象进行操作:

public class ShapeClient {
    public static void main(String[] args) {  
        Shape circle = new Circle(5);  
        Shape square = new Square(10);  
  
        // 使用 PrintAreaVisitor 来打印面积  
        ShapeVisitor areaVisitor = new PrintAreaVisitor();  
        circle.accept(areaVisitor);  
        square.accept(areaVisitor);  
  
        // 使用 PrintPerimeterVisitor 来打印周长  
        ShapeVisitor perimeterVisitor = new PrintPerimeterVisitor();  
        circle.accept(perimeterVisitor);  
        square.accept(perimeterVisitor);  
    }  
}

访问者模式的扩展

访问者(Visitor)模式常常同以下设计模式联用:

  • 与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如上面示例代码中的对象结构是用 List 实现的,它通过 List 对象的 Iterator() 方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。

  • 访问者(Visitor)模式与“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。

  • 与模板方法模式联用:可以在保持算法骨架不变的情况下,通过访问者模式为不同的对象结构添加新的操作。具体来说,可以将模板方法模式中定义的一系列步骤作为访问者接口中的方法,然后在具体的访问者实现中定义这些步骤的具体行为。这样,就可以通过创建不同的访问者来实现对同一对象结构的不同操作。
    假设我们有一个系统需要处理不同类型的文件(如文本文件、图像文件等),每种文件类型都有一系列的处理步骤(如打开、读取、处理、关闭等)。我们可以使用模板方法模式来定义这些处理步骤的算法骨架,并使用访问者模式来为每个文件类型定义具体的处理逻辑。
    首先,我们定义一个处理文件的算法骨架(模板方法模式):

public abstract class FileProcessor {  
    public final void processFile(File file) {  
        openFile(file);  
        readFile(file);  
        handleFile(file); // 这是一个钩子方法,可以在子类中覆盖  
        closeFile(file);  
    }  
      
    protected abstract void openFile(File file);  
    protected abstract void readFile(File file);  
    protected void handleFile(File file) {  
        // 默认实现,可以在子类中覆盖  
    }  
    protected abstract void closeFile(File file);  
}

然后,我们定义访问者接口,该接口包含与文件处理步骤相对应的方法:

public interface FileVisitor {  
    void visitOpenFile(File file);  
    void visitReadFile(File file);  
    void visitHandleFile(File file);  
    void visitCloseFile(File file);  
}

接着,我们为每种文件类型创建具体的访问者实现:

public class TextFileVisitor implements FileVisitor {  
    @Override  
    public void visitOpenFile(File file) {  
        // 文本文件特定的打开逻辑  
    }  
      
    @Override  
    public void visitReadFile(File file) {  
        // 文本文件特定的读取逻辑  
    }  
      
    @Override  
    public void visitHandleFile(File file) {  
        // 文本文件特定的处理逻辑  
    }  
      
    @Override  
    public void visitCloseFile(File file) {  
        // 文本文件特定的关闭逻辑  
    }  
}  
  
// 类似地,可以为图像文件等创建其他的FileVisitor实现...

最后,我们创建具体的文件处理器类,并将访问者注入其中:

public class TextFileProcessor extends FileProcessor {  
    private FileVisitor visitor;  
      
    public TextFileProcessor(FileVisitor visitor) {  
        this.visitor = visitor;  
    }  
      
    @Override  
    protected void openFile(File file) {  
        visitor.visitOpenFile(file);  
    }  
      
    @Override  
    protected void readFile(File file) {  
        visitor.visitReadFile(file);  
    }  
      
    @Override  
    protected void handleFile(File file) {  
        visitor.visitHandleFile(file);  
    }  
      
    @Override  
    protected void closeFile(File file) {  
        visitor.visitCloseFile(file);  
    }  
}

访问者模式优缺点

访问者模式是一种将数据操作与数据结构分离的设计模式,它使得你可以在不修改已有类的情况下增加新的操作。以下是访问者模式的优点和缺点:

优点:

  1. 扩展性好:访问者模式使得增加新的操作变得容易。当需要增加新的操作时,只需要增加新的访问者类即可,无需修改已有的元素类。
  2. 符合单一职责原则:访问者模式可以将原本分散在各个元素类中的操作集中起来,使得每个类的职责更加单一。
  3. 灵活性高:访问者模式可以灵活地访问和操作对象结构中的元素,通过访问者接口可以定义多种不同的访问和操作方式。
  4. 双层分离:访问者模式将数据与数据结构分离,使得两者可以独立变化,降低了系统的耦合度。

缺点:

  1. 增加系统复杂性:访问者模式增加了新的抽象层次,可能导致系统变得更加复杂。如果系统本身并不复杂,或者不需要经常增加新的操作,使用访问者模式可能会引入不必要的复杂性。
  2. 破坏封装性:访问者模式要求被访问的对象暴露一些方法和数据,这可能会破坏对象的封装性。
  3. 增加新的元素变更困难:对于访问者而言,增加新的元素类很困难,因为访问者类针对每一个元素类都提供访问操作,因此在系统中增加新的元素类,就必须修改所有访问者类,增加新的操作。这违背了“开闭原则”。
  4. 违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。

总的来说,访问者模式在某些场景下非常有用,但在选择是否使用它时,需要权衡其优缺点,并结合具体的应用场景来决定。

总结

访问者模式主要用于将操作与数据结构分离,增加新的操作变得简单,但增加新的元素类可能会导致修改访问者接口和所有的具体访问者类。以下情况可以考虑使用访问者模式:

  1. 对象结构稳定但操作易变:当有一个对象结构(如集合、数组等),其包含的元素类型比较稳定,但针对这些元素的操作经常发生变化时,Visitor模式非常适用。使用Visitor模式,你可以在不修改对象结构的情况下,通过添加新的访问者类来实现新的操作。

  2. 避免使用多重继承:在一些编程语言中,多重继承可能会引入复杂性、潜在的冲突和不易维护的代码。使用Visitor模式,你可以将操作逻辑封装在访问者类中,从而避免使用多重继承。

  3. 跨多个类进行类似操作:当需要在多个不相关的类中执行类似的操作时,Visitor模式可以帮助你将这些操作集中管理。通过将操作逻辑放在访问者类中,你可以减少代码重复,提高代码的可维护性。

  4. 操作需要访问对象内部状态:如果操作需要访问对象的内部状态,并且这些状态对于操作来说是透明的,那么使用Visitor模式可以将操作与对象状态分离。这样,你可以在不修改对象类的情况下,通过修改访问者类来改变操作的行为。

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

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

相关文章

ios逆向某易新闻 md5+aes

本期的案例比较简单,也许是ios逆向算法本来就比较简单的原因,所以前面我就多扯一些爬虫和逆向的东西。之前写的文章都是js逆向和android逆向的案例,这也是首篇ios的案例,所以会从入门开始讲起。 3大逆向对比 首先爬虫工程师大部…

Objective-C—Class底层结构探索,真心分享给你!!!

isa 走位图 在讲 OC->Class 底层类结构之前,先看下下面这张图: 通过isa走位图 得出的结论是: 1,类,父类,元类都包含了 isa, superclass 2,对象isa指向类对象,类对象的isa指向了元…

C语言操作符和数据类型的存储详解

CSDN成就一亿技术人 目录​​​​​​​ 一.操作符 一.算数操作符: 二.位移操作符: 三.位操作符: 四.赋值操作符: 五.单目操作符: 六.关系操作符: 七.逻辑操作符: 八.条件操作符&…

Java学习笔记 | JavaSE基础语法05 | 方法

文章目录 0.前言1. 方法概述2. 方法的定义和调用2.1 无参数方法定义和调用2.2 带参数方法定义和调用1 带参数方法定义和调用2 形参和实参3 带参数方法练习 2.3 带返回值方法的定义和调用1 带返回值方法定义和调用2 带返回值方法练习13 带返回值方法练习24 带返回值方法练习3 3.…

V R元宇宙平台的未来方向|V R主题馆加 盟|游戏体验馆

未来,VR元宇宙平台可能会呈现出以下发展趋势和可能性: 全面融合现实与虚拟世界: VR元宇宙平台将更加无缝地融合现实世界和虚拟世界,用户可以在虚拟环境中进行各种活动,与现实世界进行互动,并且体验到更加逼…

C程序编译、链接与项目构建

C程序编译、链接与项目构建 摘要C编译环境静、动态库介绍gcc与g和程序编译、链接Visual Studio创建和链接库动态库的显示调用 Make介绍安装使用 CMake介绍安装使用构建方式内部构建外部构建构建使用静/动态库常用[系统]变量常用指令CMake模块 Make与CMake的联系与区别 摘要 本…

代码随想录|Day26|贪心01|455.分发饼干、376.摆动序列、53.最大子数组和

455.分发饼干 大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子。 局部最优:尽量确保每块饼干被充分利用 全局最优:手上的饼干可以满足尽可能多的孩子 思路:大饼干 尽量分给 大胃口孩子 将小孩和饼干数组排序,我们从大到…

40+重量级DFLab合成模型含各种神丹底丹万能模型合集分享

之前玩DFL软件积累下来的资源,部分模型非常稀缺,之前买的都很贵,现在不玩了,分享给有缘人,懂货的自然懂。必须懂得怎么用再下载,否则对你没有任何价值。点击下载 所见即所得。其中包含几个重量级稀缺资源&…

新款理想L7一边增配一边减配,难怪大家都去买华为问界

文 | AUTO芯球 作者 | 雷歌 我真是要被理想汽车笑死了,真不愧是“定语榜单之王”。 几年前理想汽车搞了一个“中国市场新势力品牌销量周榜” 两个定语,将比亚迪,特斯拉排除在外,自己在自己打造的榜单里做了一年多的冠军宝座。…

试题E(求阶乘)

解题思路: 写不出来,看的题解。要想凑个10,就必须要有一个2和5,但是明显在一个阶乘里,因子为2的数量一定多余5的数量,所以计算5的数量。 解题代码: import java.util.Scanner; ​ public clas…

RabbitMQ 01

01.定义 02.功能

何为布控球?布控球的分类对比

主要的分类有: 根据内部的主控板卡的系统分类,典型的是基于海思芯片的嵌入式LINUX系统的,一般出国标GB28181,另外一种是剑走偏锋的安卓系统的,需要把球机的输出YUV转换为UVC接入安卓主板,作为外接USB摄像头…

Matplotlib数据可视化实战-2绘制折线图(1)

函数plot是matplotlib.pyplot模块中的一个重要函数,用于在图形中绘制折线图。其基本用法是plot(x, y, [fmt], **kwargs),其中x和y分别代表X轴和Y轴上的数据点,通常是以列表、数组或一维序列的形式给出。通常用的参数有: 基本参数…

大学教材《C语言程序设计》(浙大版)课后习题解析 | 第一、二章

概述 本文主要提供《C语言程序设计》(浙大版) 第一、二章课后习题解析,以方便同学们完成题目后作为参考对照。后续将写出三、四章节课后习题解析,如想了解更多,请持续关注该专栏。 专栏直达链接:《C语言程序设计》(浙大版)_孟俊宇…

《深入浅出LLM 》(二):大模型基石知识

🎉AI学习星球推荐: GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料,配有全面而有深度的专栏内容,包括不限于 前沿论文解读、…

抖音IP属地怎么更改

抖音是一个非常受欢迎的短视频平台,吸引了无数用户在上面分享自己的生活和才艺。然而,随着快手的火爆,一些用户开始担心自己的IP地址会被他人获取,引起个人隐私风险。那么,抖音用户又该如何更改到别的地方呢&#xff1…

ArcGIS添加天地图底图服务

目录 一、注册天地图官网、申请Key 二、ArcGis配置和使用 1、配置 2、使用 三、其他方法 一、注册天地图官网、申请Key 进入官网,并注册账号。 地址:国家地理信息公共服务平台 天地图 (tianditu.gov.cn) 点击地图API,申请Key。 注意&am…

python闭包详解(实例)

“闭包”这个词语相信大多数学过编程的同学并不陌生,但是有时候理解起来还是有一定难度。 先看定义:闭包是由函数和与其相关的引用环境组合而成的实体。比如参考资源中就有这样的的定义:在实现深约束时,需要创建一个能显式表示引…

线段树(算法思想+模板+例题)

基础思想 : 介绍 线段树可以用来维护区间信息(区间和 , 区间最值 , 区间GCD等) ,可以在log n的时间内执行区间修改 和 区间查询 ; 1 . 叶子结点的特点是左右结点相等 , 存储元素本身; 2 . 非叶子结点存储的是区间内的统计值 &…

力扣|两数相加|链表

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 …