设计模式-访问者模式

访问者模式是设计模式中行为型模式的一种(其他的还有如创建型、结构型),听说是设计模式中比较难理解的一种,最近项目中用到了该模式,所以今天总结和实践一下。

一、访问者模式要解决的问题:

稳定的数据结构和易变的操作耦合问题

二、动手实现访问者模式Demo

这里使用学校体育馆和访客的例子,体育馆里有一些场馆如羽毛球馆、篮球馆、乒乓球馆,学生可以到体育馆进行打球。

1. 不使用设计模式常规实现

先演示下没有使用访问者模式的实现,首先,学校体育馆抽象类如下:

public abstract class Gymnasium {
    protected String people;
    public Gymnasium(String people) {
        this.people = people;
    }
}

然后,几种体育场馆的实现类:

/**
 * 羽毛球馆
 */
public class BadmintonHall extends Gymnasium{
    public BadmintonHall(String people) {
        super(people);
    }

    @Override
    protected void playBall() {
        System.out.println(">>>>>>"+people+"在打羽毛球");
    }
}
/**
 * 篮球场馆
 */
public class BasketballCourt extends Gymnasium{

    public BasketballCourt(String people) {
        super(people);
    }

    @Override
    protected void playBall() {
        System.out.println(">>>>>>"+people+"在打篮球");
    }
}
/**
 * 乒乓球场馆
 */
public class TableTennisHall extends Gymnasium{

    public TableTennisHall(String people) {
        super(people);
    }

    @Override
    protected void playBall() {
        System.out.println(">>>>>>"+people+"在打乒乓球");
    }
}

测试结果

public class ClientTest {
    public static void main(String[] args) {
        List<Gymnasium> gymnasiums=new ArrayList<>();
        gymnasiums.add(new BadmintonHall("小张"));
        gymnasiums.add(new BasketballCourt("小王"));
        gymnasiums.add(new BasketballCourt("小亮"));
        gymnasiums.add(new TableTennisHall("小崔"));

        for (Gymnasium gymnasium : gymnasiums) {
            gymnasium.playBall();
        }
    }
}
// 运行结果:
>>>>>>小张在打羽毛球
>>>>>>小王在打篮球
>>>>>>小亮在打篮球
>>>>>>小崔在打乒乓球

这样就实现了我们想要的功能,试想一种情况,学校里的体育馆有时候不仅让我们课下打球,有时还需要上体育课,这个时候,每个体育场馆实现类都要增加一个上体育课的方法,这样其实违背了开闭原则,随着功能的不断增加,每个类代码会不断增多,如果我们不想频繁修改原有类,就可以按照访问者设计模式把业务操作提取到外边,访问者模式的类的基本结构如下:

  • 抽象元素(Element),如体育场馆,包含了一个accept()接口
  • 具体元素(ConcreteElement),具体的体育场馆,如篮球场馆,乒乓球场馆,实现抽象元素的accept()方法
  • 抽象访问者(Visitor),为每一个具体元素定义一个visit操作方法
  • 具体访问者(ConcreteVisitor),也就是抽取出来的方法,指明了访问者访问时具体的操作内容
  • 对象结构(ObjectStructure),一个集合,用于存放元素对象,提供让访问者遍历内部元素的方法,通常由List、Set、Map等实现。
2. 访问者模式实现

访问者接口及其实现类如下:

public interface Visitor {
    void visit(BadmintonHall badmintonHall);
    void visit(BasketballCourt basketballCourt);
    void visit(TableTennisHall tableTennisHall);
}

public class PlayBallVisitor implements Visitor{
    @Override
    public void visit(BadmintonHall badmintonHall) {
        System.out.println(">>>>>>"+badmintonHall.people+"在打羽毛球");
    }

    @Override
    public void visit(BasketballCourt basketballCourt) {
        System.out.println(">>>>>>"+basketballCourt.people+"在打篮球");
    }

    @Override
    public void visit(TableTennisHall tableTennisHall) {
        System.out.println(">>>>>>"+tableTennisHall.people+"在打乒乓球");
    }
}

学校体育馆及其实现类

public abstract class Gymnasium {

    protected String people;

    public Gymnasium(String people) {
        this.people = people;
    }

    abstract void accept(Visitor visitor);
}
/**
 * 羽毛球馆
 */
public class BadmintonHall extends Gymnasium{
    public BadmintonHall(String people) {
        super(people);
    }

    @Override
    void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
/**
 * 篮球场
 */
public class BasketballCourt extends Gymnasium{

    public BasketballCourt(String people) {
        super(people);
    }

    @Override
    void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
/**
 * 乒乓球场馆
 */
public class TableTennisHall extends Gymnasium{

    public TableTennisHall(String people) {
        super(people);
    }
    @Override
    void accept(Visitor visitor) {
        visitor.visit(this);
    }

}

客户端测试,运行结果和之前相同

public class Client {
    public static void main(String[] args) {
        List<Gymnasium> gymnasiums=new ArrayList<>();
        gymnasiums.add(new BadmintonHall("小张"));
        gymnasiums.add(new BasketballCourt("小王"));
        gymnasiums.add(new BasketballCourt("小亮"));
        gymnasiums.add(new TableTennisHall("小崔"));

        PlayBallVisitor playBallVisitor=new PlayBallVisitor();
        for (Gymnasium gymnasium : gymnasiums) {
            gymnasium.accept(playBallVisitor);
        }
    }
}
// 运行结果如下:
>>>>>>小张在打羽毛球
>>>>>>小王在打篮球
>>>>>>小亮在打篮球
>>>>>>小崔在打乒乓球
3.试想一次扩展

假如新增一个上体育课的操作方法,此时只需要增加一个Visitor的实现类即可

public class PEVisitor implements Visitor{
    @Override
    public void visit(BadmintonHall badmintonHall) {
        System.out.println(">>>>>>"+badmintonHall.people+"在羽毛球馆教羽毛球课");
    }

    @Override
    public void visit(BasketballCourt basketballCourt) {
        System.out.println(">>>>>>"+basketballCourt.people+"在篮球馆指导学生练习篮球");
    }

    @Override
    public void visit(TableTennisHall tableTennisHall) {
        System.out.println(">>>>>>"+tableTennisHall.people+"在乒乓球馆指导学生练习乒乓球");
    }
}

测试结果如下:

public class Client {
    public static void main(String[] args) {
        List<Gymnasium> stuGymnasiums=new ArrayList<>();
        stuGymnasiums.add(new BadmintonHall("小张"));
        stuGymnasiums.add(new BasketballCourt("小王"));
        stuGymnasiums.add(new BasketballCourt("小亮"));
        stuGymnasiums.add(new TableTennisHall("小崔"));

        List<Gymnasium> gymnasiums=new ArrayList<>();
        gymnasiums.add(new BadmintonHall("张老师"));
        gymnasiums.add(new BasketballCourt("王老师"));
        gymnasiums.add(new TableTennisHall("李老师"));

        PlayBallVisitor playBallVisitor=new PlayBallVisitor();
        for (Gymnasium stuGymnasium : stuGymnasiums) {
            stuGymnasium.accept(playBallVisitor);
        }
        PEVisitor peVisitor=new PEVisitor();
        for (Gymnasium gymnasium : gymnasiums) {
            gymnasium.accept(peVisitor);
        }
    }
}
// 运行结果
>>>>>>小张在打羽毛球
>>>>>>小王在打篮球
>>>>>>小亮在打篮球
>>>>>>小崔在打乒乓球
>>>>>>张老师在羽毛球馆教羽毛球课
>>>>>>王老师在篮球馆指导学生练习篮球
>>>>>>李老师在乒乓球馆指导学生练习乒乓球

可以看到,访问者模式实现的关键是对Vistior类的抽取,以及每个元素Element有个关键的方法accept(),来接受Visitor的访问,accept方法在调用 visitor.visit(this)时候,就可以拿到当前具体元素的信息进行操作了。上面例子的类图如下:
在这里插入图片描述

三、访问者模式的应用场景

我们需要对一组对象进行一些业务操作,但为了避免不断的在对象中添加功能“污染”到原来的对象,导致类越来越臃肿,职责不够单一,可以使用访问者模式,将对象新增加的业务操作抽离出来,放在具体的访问者接口和实现类中。

实际应用中,如复杂的嵌套结构访问就可以使用访问者模式,如对文件系统的遍历,文件系统是一个树状结构,包含文件和文件夹等元素。使用访问者模式可以设计一个访问者,用于执行不同的操作,比如计算文件夹大小、统计文件数量等。如java.nio.file包中的FileVistor类,就用到了访问者模式。

四、优缺点

优点
  • 符合单一职责原则;
  • 新增新访问操作,只需要实现一个访问类,符合开闭原则;
  • 灵活、易扩展;
缺点
  • 代码可读性差,不了解该模式,则难以理解;
  • 新增新元素的时候,需要修改抽象访问者和具体访问者实现类,违背开闭原则;
  • 访问某个元素时,可能没有访问元素私有成员变量和方法的必要权限

五、访问者模式中的伪动态双分派概念

访问者模式中的伪动态双分派,是指在执行操作时,根据两个元素的类型动态地选择执行哪个方法。这个"伪动态双分派"是因为在很多编程语言中,并没有真正支持双分派(double dispatch),而是通过一些技巧来实现类似的效果。上面代码中我们元素实现了

@Override
    void accept(Visitor visitor) {
        visitor.visit(this);
    }

这里的伪动态双分派体现在accept方法的调用上。第一次单分派,是执行的哪一个元素的accept方法,第二次分派是执行的哪个visitor的visit方法。根据元素类型和访问者类型的动态选择方法的调用,从而达到双分派的效果。

分派的概念,就是指在运行时确定程序应该调用哪个具体的方法或函数的过程。分派的方式可以分为静态分派和动态分派两种,静态分派在编译器,如方法重装,动态分派在运行期,如方法重写。

参考文档:
https://refactoringguru.cn/design-patterns/visitor

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【系统架构】集群、分布式概念及系统架构演进过程

集群、分布式概念&#xff1a; 对食物没有太高要求的人在肚子饿的时候一般都会选择去兰州拉面、沙县小吃等小饭馆&#xff0c;这类小饭馆有个很显著的特点&#xff1a;洗菜、切菜、炒菜都是同一个人完成&#xff0c;如果厨子不舒服可能饭馆还会歇业。而一些人流量较大的饭馆的分…

MATLAB 平面拟合并可视化(34)

MATLAB 平面拟合并可视化(34) 一、效果二、代码一、效果 二、代码 % 生成三维点数据 x = rand(100, 1); y = rand(100, 1

nest框架的token登录,以及token校验

1.搭建项目 项目初始化&#xff1a; npm i -g nestjs/cli nest new project-name 在终端下执行这四个命令&#xff0c;生成两个新模块&#xff1a; nest g module auth nest g service auth nest g module users nest g service users 然后把这三个文件删掉&#xff0c;是没有…

【LeetCode刷题笔记(4)】【Python】【移动零】【简单】

文章目录 题目描述示例 1示例 2提示 解决方案题意拆解双指针算法双指针法的主要优点双指针法的使用场景举例&#xff1a; 解决方案&#xff1a;【双指针一次遍历】解题心得方案代码运行结果复杂度分析 结束语 移动零 题目描述 给定一个数组 nums&#xff0c;编写一个函数将所…

OWASP ESAPI 预防XSS跨站脚本攻击

跨站脚本攻击XSS案例&#xff1a;跨站脚本攻击XSS案例及其解决方案_xss攻击案例-CSDN博客 Java集成&#xff1a; 1、引入maven <!--OWASP ESAPI&#xff0c;防御 XSS跨站攻击--><dependency><groupId>org.owasp.esapi</groupId><artifactId>esa…

ArrayList集合的两个实例应用,有趣的洗牌算法与杨辉三角

本节课的内容&#xff0c;就让我们来学习一下ArrayList集合的应用&#xff0c;ArrayList的本质就是一个顺序表&#xff0c;那下面一起来学习吧 目录 一、杨辉三角 1.题目详情及链接 2.剖析题目 3.思路及代码 二、洗牌算法 1.创造牌对象 2.创造一副牌 3.洗牌操作 4.发…

Alibaba分布式事务组件Seata AT实战

1. 分布式事务简介 1.1 本地事务 大多数场景下&#xff0c;我们的应用都只需要操作单一的数据库&#xff0c;这种情况下的事务称之为本地事务(Local Transaction)。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所示&#xff1a; 在JDBC编程中&#xff0c;我…

力扣24 两两交换链表中的节点 Java版本

文章目录 题目解题方法Code 题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;hea…

事务隔离级别:保障数据库并发事务的一致性与性能

目录 引言 1. 事务隔离级别概述 2. 读未提交隔离级别 3. 读已提交隔离级别 4. 可重复读隔离级别 5. 串行化隔离级别 6. 保障事务隔离级别的机制 7. 如何选择合适的隔离级别 8. 结语 引言 在数据库管理系统&#xff08;DBMS&#xff09;中&#xff0c;事务隔离级别是一…

12.14_黑马数据结构与算法笔记Java

目录 120 二叉搜索树 min max 121 二叉搜索树 put 122 二叉搜索树 前任后任1 123 二叉搜索树 前任后任2 124 二叉搜索树 删除1 125 二叉搜索树 删除2 126 二叉搜索树 删除3 127 二叉搜索树 删除 递归1 128 二叉搜索树 删除 递归2 129 二叉搜索树 范围查询 130 二叉搜…

ADC学习总结

ADC的架构分类&#xff1a; 1、Delta-Sigma 采样率一般是在1M以内&#xff0c;位数一般可以做的很高&#xff0c;比如24位&#xff0c;Delta-Sigma ADC采用了过采样技术&#xff0c;不需要在模拟输入端加抗混叠滤波&#xff0c;由后端数字滤波器进行处理&#xff0c;通过信噪…

网工内推 | IT经理,50k*14薪,NP以上即可,七险一金

01 海天瑞声 招聘岗位&#xff1a;IT经理 职责描述&#xff1a; 1、IT基础架构的方案制定、实施和日常维护&#xff0c;包括机房建设运维、服务器配置及运维、网络规划及运维、上网行为管理、电话、电话、监控、门禁等各类弱电系统搭建及运维 2、负责公司环境及网络安全防御体…

WEB服务器介绍

Web服务器是指驻留于因特网上某种类型计算机的程序。当Web浏览器连到服务器上并请求文件时&#xff0c;服务器将处理该请求并将文件发送到该浏览器上&#xff0c;附带的信息会告诉浏览器如何查看该文件&#xff0c;即文WEB服务器件类型。服务器使用HTTP进行信息交流&#xff0c…

ASF-YOLO开源 | SSFF融合+TPE编码+CPAM注意力,精度提升!

目录 摘要 1 Introduction 2 Related work 2.1 Cell instance segmentation 2.2 Improved YOLO for instance segmentation 3 The proposed ASF-YOLO model 3.1 Overall architecture 3.2 Scale sequence feature fusion module 3.3 Triple feature encoding module …

Kvaser Leaf v3 重磅上新!报文速率高达20000条/秒!支持CAN FD!EAN: 73-30130-01424-4

作为CAN总线领域的专家&#xff0c;Kvaser深耕行业40年&#xff0c;至今已经累计推出100多款CAN产品。其中稳定小巧、便携易用的Kvaser经典Leaf系列是将计算机与CAN网络连接并获取CAN/CAN FD数据的最简单、性价比最高的方法之一。Kvaser秉持着将用户放在重要位置的原则&#xf…

6.5.编解码器信息的收集

那在上节课中呢&#xff1f;我向你介绍了add track相关的内容&#xff0c;那今天呢&#xff1f;我们来看看编解码器信息的收集。那在这里呢&#xff0c;我们需要问几个重要的问题&#xff0c;那首先呢&#xff0c;就是我们上节课通过&#xff0c;可以让web rtc知道我们都要传输…

智能优化算法应用:基于旗鱼算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于旗鱼算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于旗鱼算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.旗鱼算法4.实验参数设定5.算法结果6.参考文献7.MA…

优先考虑静态成员类

在Java中&#xff0c;静态成员类&#xff08;static nested class&#xff09;是一种嵌套在另一个类中的类&#xff0c;且被声明为静态。静态成员类不依赖于外部类的实例&#xff0c;可以直接通过外部类的类名来访问。 优先考虑使用静态成员类的情况通常是当这个类与外部类的实…

一文带你了解UI自动化测试框架

PythonSeleniumUnittestDdtHTMLReport分布式数据驱动自动化测试框架结构 1、Business&#xff1a;公共业务模块&#xff0c;如登录模块&#xff0c;可以把登录模块进行封装供调用 ------login_business.py from Page_Object.Common_Page.login_page import Login_Page from H…

探秘闭包:隐藏在函数背后的小秘密(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…