《设计模式》访问者模式

news2025/2/26 6:58:16

《设计模式》访问者模式

定义

  • 访问者模式用于封装一些作用于某种数据结构中的各元素的操作,将数据结构和数据操作分离,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作
  • 属于行为型模式

访问模式的角色组成

  • Visitor(抽象访问者):抽象访问者为对象结构中每个具体元素声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型。具体访问者需要实现这些操作方法,提供对这些元素的访问操作。
  • ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每个操作用于访问对象结构中一种类型的元素。
  • Element(抽象元素):抽象元素定义一个 accept() 方法,参数为抽象访问者。
  • ConcreteElement(具体元素):具体元素实现了 accept() 方法,在 accept() 方法中调用访问者的访问方法以便完成对一个元素的操作。
  • ObjectStructure(对象结构):对象结构是一个元素的集合,用于存放元素对象,并且提供了遍历其内部元素的方法。

访问者模式的 UML 类图

在这里插入图片描述

🎈情景案例:目前汽车市场上有三种主流类型的能源汽车,分别为:1、电动汽车。2、油电混动汽车。3、传统油车。汽车购买者一般在购买之前,会对这三种能源类型的汽车进行多方面的比较,例如油耗、续航里程、价格等方面,从而综合考虑选择适合自己的能源汽车。我们以消费者购买能源汽车之前综合对比的情景,使用访问者模式对该情景进行编码。

UML 类图

在这里插入图片描述

抽象元素 Car 接口

public interface Car {
    void accept(Visitor visitor);
}

具体元素 ElectroCar 类

public class ElectroCar implements Car {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

具体元素 OilCar 类

public class OilCar implements Car {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

具体元素 HybridCar 类

public class HybridCar implements Car {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

抽象访问者 Visitor 接口

public interface Visitor {
    void visit(ElectroCar electroCar);
    void visit(OilCar oilCar);
    void visit(HybridCar hybridCar);
}

具体访问者 FuelVisitor 类

public class FuelVisitor implements Visitor {

    @Override
    public void visit(ElectroCar electroCar) {
        System.out.println("电车不需要燃油,只要充电");
    }

    @Override
    public void visit(OilCar oilCar) {
        System.out.println("燃油车需要燃油");
    }

    @Override
    public void visit(HybridCar hybridCar) {
        System.out.println("混动车需要充电,也需要燃油,但是相较于油车减少了燃油");
    }
}

具体访问者 EnduranceVisitor 类

public class EnduranceVisitor implements Visitor {
    @Override
    public void visit(ElectroCar electroCar) {
        System.out.println("电车的续航里程一般较短,取决于电车容量大小");
    }

    @Override
    public void visit(OilCar oilCar) {
        System.out.println("燃油车续航里程相对较长,取决于油箱容量");
    }

    @Override
    public void visit(HybridCar hybridCar) {
        System.out.println("混动车续航里程相对较长,取决于油箱容量和电池容量的大小");
    }
}

具体访问者 PriceVisitor 类

public class PriceVisitor implements Visitor {
    @Override
    public void visit(ElectroCar electroCar) {
        System.out.println("电车作为新势力价格相对较贵");
    }

    @Override
    public void visit(OilCar oilCar) {
        System.out.println("燃油车价格相对较低");
    }

    @Override
    public void visit(HybridCar hybridCar) {
        System.out.println("混动车价格相对于油车略高,相对于电车价格略低");
    }
}

对象结构 ObjectStructure 类

public class ObjectStructure {
    private List<Car> cars = new ArrayList<>();

    public void attach(Car car) {
        cars.add(car);
    }

    public void detach(Car car) {
        cars.remove(car);
    }

    public void accept(Visitor visitor) {
        Iterator<Car> iterator = cars.iterator();
        while (iterator.hasNext()) {
            iterator.next().accept(visitor);
        }
    }
}

客户端 Client 类

public class Client {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new ElectroCar());
        objectStructure.attach(new OilCar());
        objectStructure.attach(new HybridCar());

        FuelVisitor fuelVisitor = new FuelVisitor();
        EnduranceVisitor enduranceVisitor = new EnduranceVisitor();
        PriceVisitor priceVisitor = new PriceVisitor();

        System.out.println("----------油耗对比-----------");
        objectStructure.accept(fuelVisitor);
        System.out.println("----------续航里程对比-----------");
        objectStructure.accept(enduranceVisitor);
        System.out.println("---------价格对比-----------");
        objectStructure.accept(priceVisitor);
    }
}

如果在上述情景案例中增加了一种新的具体访问类,例如对比车型的选择数量,那么无需修改源代码,只要增加一个新的具体访问者类即可。在该具体访问者中封装了新的操作元素对象的方法。从增加新的访问者的角度来看,访问者模式符合开闭原则。

但是,如果要在上述情景案例中增加一种新的具体元素,例如增加氢能汽车。由于原有情境中并未相应的访问接口(在抽象访问者中没有声明访问“氢能汽车”的方法),因此必须对原有情景案例代码进行修改,在原有的抽象访问者类和具体访问者类中增加相应的访问方法。从增加新的元素的角度来看,访问者模式违背了开闭原则。

因此,访问者模式的优点

  • 增加新的访问操作很方便,使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无需修改源代码,符合开闭原则。
  • 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

访问者模式的缺点

  • 无法增加新的元素类型,如果系统数据结构发生变化,则访问者类必须增加对应元素类型的操作,违背了开闭原则。
  • 破坏封装,访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

访问者模式的适用场景

  • 系统中数据结构固定不变化,经常需要在已有的数据结构上定义新的数据操作。
  • 需要对一个对象结构中的对象进行很多不同且不相关的操作,并且需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。

🎈访问者模式在JDK源码中的应用

java.nio.file 包下的接口 FileVisitor,它定义了文件树中所有节点的访问方法,提供递归遍历文件树的支持,该接口有四个关键方法:

public interface FileVisitor<T> {
	// 在访问目录之前调用
    FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
        throws IOException;
	// 访问文件时调用
    FileVisitResult visitFile(T file, BasicFileAttributes attrs)
        throws IOException;
	// 在访问文件失败时调用
    FileVisitResult visitFileFailed(T file, IOException exc)
        throws IOException;
    // 在访问目录之后调用
    FileVisitResult postVisitDirectory(T dir, IOException exc)
        throws IOException;
}

FileVisitor 接口的实现类可以用来递归地访问一个目录及其子目录中的所有文件和子目录。通过实现 FileVisitor 接口中的方法,可以对目录和文件进行不同的操作。

例如,JDK 给出了 FileVisitor 接口的实现类示例 SimpleFileVisitor,定义了对文件和文件夹目录的简单数据操作。

public class SimpleFileVisitor<T> implements FileVisitor<T> {

    protected SimpleFileVisitor() {
    }
    
    @Override
    public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
        throws IOException
    {
        Objects.requireNonNull(dir);
        Objects.requireNonNull(attrs);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult visitFile(T file, BasicFileAttributes attrs)
        throws IOException
    {
        Objects.requireNonNull(file);
        Objects.requireNonNull(attrs);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult visitFileFailed(T file, IOException exc)
        throws IOException
    {
        Objects.requireNonNull(file);
        throw exc;
    }

    @Override
    public FileVisitResult postVisitDirectory(T dir, IOException exc)
        throws IOException
    {
        Objects.requireNonNull(dir);
        if (exc != null)
            throw exc;
        return FileVisitResult.CONTINUE;
    }
}

⭐在JDK中,FileVisitor 接口主要应用于 File 类和 Path 类中的 walkFileTree() 方法中,这个方法会遍历指定目录及其子目录中的所有文件和子目录,同时调用实现了 FileVisitor 接口的类的相应方法,从而实现对这些文件和子目录的访问和处理。

假设我们要在一个目录及其子目录中查找所有的 .java 文件,并输出它们的路径。我们可以实现一个 FileVisitor 接口来完成这个任务。

public class FindJavaFiles extends FileVisitor<T> {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        if (file.toString().endsWith(".java")) {
            System.out.println(file);
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        System.err.println(exc);
        return FileVisitResult.CONTINUE;
    }
}

之后在客户端可以使用 Path 类中的 walkFileTree() 方法来递归地访问指定目录中的所有文件和子目录,并调用我们实现的 FindJavaFiles 类中的 visitFile() 和 visitFileFailed() 方法。

FindJavaFiles 类

public class FindJavaFiles implements FileVisitor {
    @Override
    public FileVisitResult preVisitDirectory(Object dir, BasicFileAttributes attrs) throws IOException {
        Objects.requireNonNull(dir);
        Objects.requireNonNull(attrs);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Object file, BasicFileAttributes attrs) throws IOException {
        if (file.toString().endsWith(".java")) {
            System.out.println(file);
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Object file, IOException exc) throws IOException {
        System.err.println(exc);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Object dir, IOException exc) throws IOException {
        Objects.requireNonNull(dir);
        if (exc != null) {
            throw exc;
        }
        return FileVisitResult.CONTINUE;
    }
}

客户端类

public class Main {
    public static void main(String[] args) throws IOException {
        Path startDir = Paths.get("/path/to/directory");
        Files.walkFileTree(startDir, new FindJavaFiles());
    }
}

🌹最后,欢迎大家在评论区积极讨论关于访问者模式的各种问题,让我们更好地了解访问者模式!

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

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

相关文章

基本数据类型不一定存储在栈中,是不是颠覆了你的认知

大家好&#xff0c;我是三叔&#xff0c;很高兴这期又和大家见面了&#xff0c;有很多小伙伴问我&#xff0c;基本数据类型一定在栈内存中吗&#xff1f;网上答案也是五花八门&#xff0c;部分读者都有被误导过&#xff0c;基本数据类型不一定在栈内存中&#xff01; 虽然基本…

使用Lychee荔枝图床+cpolar内网穿透快速搭建稳定的私人图床【无需公网IP】

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

【Linux内核解析-linux-5.14.10-内核源码注释】自旋锁spinlock机制

自旋锁 Note: 在使用自旋锁时应该避免长时间持有锁&#xff0c;否则可能会导致其他线程或进程无法访问共享资源。因此&#xff0c;建议将锁的持有时间尽量缩短&#xff0c;以提高系统的并发性能。 Linux中的自旋锁机制是一种用于同步多个线程或进程访问共享资源的技术。当一个…

一分钟图情论文:《公共图书馆法》视域下的馆员知识与能力体系探究

一分钟图情论文&#xff1a;《公共图书馆法》视域下的馆员知识与能力体系探究 在公共服务体系建设过程中&#xff0c;图书馆建设是十分关键地一环&#xff0c;在图书馆建设过程中又以图书馆员队伍的建设首当其冲。在当今复杂的信息环境下&#xff0c;我们该如何培养图书馆员&a…

语言与专业的奇迹:如何利用ChatGPT优化跨国贸易

贸易公司&#xff0c;在进行跨国贸易时&#xff0c;往往需要面对不同国家的甲方或者乙方&#xff0c;在与之沟通的过程中&#xff0c;语言和专业是必须要过的一关&#xff0c;顺畅的交流&#xff0c;往往会带来更好的收益。 今天以“茶”为例&#xff0c;给大家介绍一“知否AI…

Nacos 服务网格2

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

cam_lidar_calibration标定速腾激光雷达和单目相机外参

目录 一、资源链接二、代码测试2.1安装依赖2.2代码下载和修改2.2.1 optimiser.h文件2.2.2 feature_extractor.h文件 2.3编译代码2.4测试数据集2.4.1迭代计算2.4.2查看校准结果 三、标定自己激光雷达和相机3.1修改代码3.1.1camera_info.yaml配置文件3.1.2params.yaml配置文件3.1…

跳槽,如果没有更好的选择,可以去美团试试···

在美团干了半年&#xff0c;说一下自己的感受&#xff0c;美团是一家福利中等&#xff0c;工资待遇中上&#xff0c;高层管理团队强大&#xff0c;加班强度一般&#xff0c;技术不错&#xff0c;办公环境一般&#xff0c;工作氛围中上&#xff0c;部门差距之间工作体验差距巨大…

阿里巴巴官方上线!号称国内Java八股文天花板(终极版)首次开源

铜三铁四已经结束了&#xff0c;但还是有很多Java程序员没有找到工作或者成功跳槽&#xff0c;跳槽成功的也只是从一个坑中&#xff0c;跳入另一个坑中…… 在LZ看来&#xff0c;真正有意义的就业与跳槽&#xff0c;是要进入到一个有绝对潜力的行业或者薪资能实现爆炸式增长的。…

Science Advances:宋艳课题组发现经颅近红外激光刺激可提升人类工作记忆

图1. 新闻稿封面 工作记忆——在几秒钟内主动“记住”有用信息的能力——在许多高级认知活动中起着至关重要的作用。由于工作记忆能力的个体差异可以预测流体智力和广泛的认知功能&#xff0c;这使得提高工作记忆能力成为干预和增强的有吸引力的目标。 美国食品及药品管理局声…

SpringSecurity 一文彻底掌握

文章目录 前言一、SpringSecurity Web方案&#x1f353;Test Controller 测试请求控制器&#x1f923;SpringSecurity 基本原理&#x1f30d;代码底层流程&#xff1a;重点看三个过滤器FilterSecurityInterceptor 方法级的权限过滤器ExceptionTranslationFilter 异常过滤器User…

智能玩具机器人语音识别方案——NRK3301离线语音IC

机器人玩具已经成为儿童玩具和教育用品的主流&#xff0c;它不仅能充分激发和满足了儿童消费群体的好奇心&#xff0c;同时还能强化了消费群体和玩具的互动体验。 机器人玩具主要是通过语音识别技术&#xff0c;让我们可以与玩具对话&#xff0c;可以用语音对玩具发出命令&…

ENVI实现基于像元方法的栅格图像镶嵌拼接(所有图像无需地理信息)

本文介绍基于ENVI软件&#xff0c;利用“Pixel Based Mosaicking”工具实现栅格遥感影像镶嵌拼接的方法。 首先需要说明的是&#xff0c;本文需要镶嵌的遥感影像并不含地理参考信息&#xff0c;因此仅可以使用ENVI中的“Pixel Based Mosaicking”工具&#xff08;该工具可以对…

SpringMVC简介、请求与响应、REST风格、SSM整合、拦截器

目录 SpringMVC简介 SpringMVC概述 入门案例 入门案例工作流程分析 Controller加载控制 PostMan 请求与响应 设置请求映射路径 五种类型参数传递 JSON数据传输参数 JSON对象数据 JSON对象数组 日期类型参数传递 响应 REST风格 REST风格简介 RESTful入门案例…

前后端分离实现社区销售系统

在当今的互联网时代&#xff0c;社区销售系统越来越普及。这种系统可以方便地管理商品、订单以及会员等信息&#xff0c;使得销售过程更加高效和便利。本文将介绍如何通过前后端分离的方式实现一个社区销售系统。 需求分析 社区销售系统主要包括会员管理、商品管理、订单管理…

C++ ---- 类和对象(中)

目录 类的默认成员函数介绍 构造函数 构造函数概念 构造函数特性 析构函数 析构函数概念 析构函数特性 拷贝构造 拷贝构造概念 拷贝构造特点 赋值重载 赋值重载介绍 赋值重载特性 取地址重载和const取地址重载 const成员 取地址和const取地址重载 类的默认成员函…

【致敬未来的攻城狮计划】— 连续打卡第三十天:总结与回顾

学习目标&#xff1a; 自2023年4月13日开始&#xff0c;我参加了为期一个月的【致敬未来的攻城狮计划】&#xff0c;今天是第三十天&#xff0c;做一个总结和回顾。 我参加的是【致敬未来的攻城狮计划】第二期&#xff08;攻城狮计划&#xff09; 在这里首先还是感谢 李…

【云服务器】关于UDP/TCP跨平台网络通信服务器无响应的情况及解决办法

关于跨平台网络通信服务器无反应的情况 一、问题出现二、云服务器Centos7防火墙开放端口2.1 检查防火墙状态2.2 开启防火墙2.3 在running 状态下&#xff0c;向firewall 添加需要开放的端口2.4 重新加载防火墙配置2.5 查看端口是否放开 三、云服务器防火墙配置开放端口3.1 进入…

决策树与随机森林

决策树解决回归问题时进行平均数计算。 决策树 (1)熵&#xff08;entropy)与特征节点 熵&#xff08;entropy&#xff09;&#xff0c;度量着信息的不确定性&#xff0c;信息的不确定性越大&#xff0c;熵越大。信息熵和事件发生的概率成反比。 ■信息熵代表随机变量的复杂度…

c++《list容器的使用》

本文主要介绍list的一些常见接口的使用 文章目录 一、list的介绍二、list的使用2.1 list的构造函数2.2 list迭代器的使用2.3 list相关的容量大小相关的函数2.4 list数据的访问相关的函数2.5 list的数据调整相关的函数2.6 list中其他函数操作 一、list的介绍 list是可以以O(1)的…