设计模式之访问者模式

news2024/11/17 12:40:20

Visitor design pattern

访问者模式的概念、访问者模式的结构、访问者模式的优缺点、访问者模式的使用场景、访问者模式实现示例、访问者模式的源码分析、双分派


1、访问者模式的概念

  访问者模式,即在不改变聚合对象内元素的前提下,为聚合对象内每个元素提供多种访问方式,即聚合对象内的每个元素都有多个访问者对象。访问者模式主要解决稳定的数据结构和易变元素的操作之间的耦合问题。

2、访问者模式的结构

  • 抽象元素:定义接受访问者的行为。
  • 具体元素:实现抽象访问者,实现其定义的接受访问者的行为,并将访问行为委托给接受的访问者对象。
  • 抽象访问者:定义访问不同元素的行为。即当系统中有多个不同类型的元素时,抽象访问者则需要定义多个访问行为。
  • 具体访问者:实现抽象访问者,实现其定义的多个访问不同元素的行为。
  • 对象结构:即聚合对象,持有一个抽象元素的聚合引用,并提供添加元素、获取元素、移除元素、访问元素的方法。

visitor-class

3、访问者模式的优缺点

  • 优点:
    • 符合单一职责原则,即数据的存储和操作分别由对象结构类和访问者类实现。
    • 优秀的扩展性和灵活性。
  • 缺点:
    • 具体元素对访问者公布了其细节,违反了迪米特法则。
    • 具体元素的增加将导致访问者类的修改,违反了开闭原则。
    • 访问者类依赖了具体类而不是抽象,违反了依赖倒置原则。

4、访问者模式的使用场景

  • 当对象结构相对稳定,但其操作算法经常变化时。
  • 当需要为对象结构中的元素提供多个不同且不想管的操作,且要避免让这些操作的变化影响对象的结构时。

5、访问者模式的实现示例

抽象元素:

public interface Element {

    /**
     * 接受访问者
     * @param visitor
     */
    void accept(Visitor visitor);
}

具体元素一:

public class OneElement implements Element {

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

具体元素二:

public class TwoElement implements Element {

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

抽象访问者:

public interface Visitor {

    /**
     * 访问元素一
     * @param oneElement
     */
    void visitor(OneElement oneElement);

    /**
     * 访问元素二
     * @param twoElement
     */
    void visitor(TwoElement twoElement);
}

具体访问者一:

public class OneVisitor implements Visitor {

    @Override
    public void visitor(OneElement oneElement) {
        System.out.println("访问者一访问元素一 " + oneElement);
    }

    @Override
    public void visitor(TwoElement twoElement) {
        System.out.println("访问者一访问元素二 " + twoElement);
    }
}

具体访问者二:

public class TwoVisitor implements Visitor {

    @Override
    public void visitor(OneElement oneElement) {
        System.out.println("访问者二访问元素一 " + oneElement);
    }

    @Override
    public void visitor(TwoElement twoElement) {
        System.out.println("访问者二访问元素二 " + twoElement);
    }
}

对象结构:

public class ObjectStructure {

    private List<Element> elements;

    public ObjectStructure() {
        this.elements = new ArrayList<>();
    }

    public void add(Element element) {
        this.elements.add(element);
    }

    public Element get(Integer index) {
        return this.elements.get(index);
    }

    public void remove(Element element) {
        this.elements.remove(element);
    }

    /**
     * 访问对象内元素
     * @param visitor
     */
    public void accept(Visitor visitor) {
        for (Element element : this.elements) {
            element.accept(visitor);
        }
    }
}

测试:

public class VisitorTest {

    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.add(new OneElement());
        objectStructure.add(new OneElement());
        objectStructure.add(new TwoElement());
        objectStructure.add(new TwoElement());

        objectStructure.accept(new OneVisitor());
        System.out.println();
        objectStructure.accept(new TwoVisitor());
    }
}

测试结果:

访问者一访问元素一 org.xgllhz.designpattern.behaviortype.visitor.OneElement@51efea79
访问者一访问元素一 org.xgllhz.designpattern.behaviortype.visitor.OneElement@5034c75a
访问者一访问元素二 org.xgllhz.designpattern.behaviortype.visitor.TwoElement@396a51ab
访问者一访问元素二 org.xgllhz.designpattern.behaviortype.visitor.TwoElement@51081592

访问者二访问元素一 org.xgllhz.designpattern.behaviortype.visitor.OneElement@51efea79
访问者二访问元素一 org.xgllhz.designpattern.behaviortype.visitor.OneElement@5034c75a
访问者二访问元素二 org.xgllhz.designpattern.behaviortype.visitor.TwoElement@396a51ab
访问者二访问元素二 org.xgllhz.designpattern.behaviortype.visitor.TwoElement@51081592

6、访问者模式的源码分析

404

7、双分派技术

  访问者模式的实现使用了一种双分派技术。

7.1、分派

  变量被声明时的类型叫做变量的静态类型,又称为明显类型;而变量所引用的对象的真实类型叫做变量的实际类型。如 Map<String, Object> map = new HashMap<>(),map 变量的静态类型是 Map,实际类型是 HashMap。根据对象的类型对方法进行选择,就是分派,即 Dispatch,分派又分为两种,即静态分派和动态分派。

  • 静态分派:

    静态分派发生在编译期,分派由变量的静态类型决定。方法重载就是静态分派。

  • 动态分派:

    动态分派发生在运行期,分派由变量的实际类型决定。方法重写就是动态分派。

7.2、静态分派

通过方法重载支持静态分派。

public class Hero {
}

public class Zed extends Hero {
}

public class Fizz extends Hero {
}
public class Execute {

    public void execute(Hero hero) {
        System.out.println("hero");
    }

    public void execute(Zed zed) {
        System.out.println("zed");
    }

    public void execute(Fizz fizz) {
        System.out.println("fizz");
    }
}

测试:

public class StaticDispatchTest {

    public static void main(String[] args) {
        Execute execute = new Execute();

        Hero hero = new Hero();
        Hero zed = new Zed();
        Hero fizz = new Fizz();

        execute.execute(hero);
        execute.execute(zed);
        execute.execute(fizz);
    }
}

测试结果:

hero
hero
hero

  静态分派是根据变量的静态类型选择的,且是在编译期选择的,于是变量在编译期就选择了 Hero 类型,即变量的静态类型。

7.3、动态分派

通过方法重写来支持动态分派。

public class Hero {

    public void execute() {
        System.out.println("hero");
    }
}

public class Zed extends Hero {

    @Override
    public void execute() {
        System.out.println("zed");
    }
}

public class Fizz extends Hero {

    @Override
    public void execute() {
        System.out.println("fizz");
    }
}

测试:

public class DynamicDispatchTest {

    public static void main(String[] args) {
        Hero hero = new Hero();
        Hero zed = new Zed();
        Hero fizz = new Fizz();

        hero.execute();
        zed.execute();
        fizz.execute();
    }
}

测试结果:

hero
zed
fizz

  动态分派是在运行期根据变脸的实际类型决定的,即三个变量的实际类型分别是 Hero、Zed、Fizz。

7.4、双分派

  双分派,即选择方法时,不仅要根据方法调用者(即消息接受者)的实际类型来决定,还要根据方法入参的实际类型来决定。

public class Hero {

    public void accept(Execute execute) {
        execute.execute(this);
    }
}

public class Zed extends Hero {

    @Override
    public void accept(Execute execute) {
        execute.execute(this);
    }
}

public class Fizz extends Hero {

    @Override
    public void accept(Execute execute) {
        execute.execute(this);
    }
}
public class Execute {

    public void execute(Hero hero) {
        System.out.println("hero");
    }

    public void execute(Zed zed) {
        System.out.println("zed");
    }

    public void execute(Fizz fizz) {
        System.out.println("fizz");
    }
}

测试:

public class DoubleDispatchTest {

    public static void main(String[] args) {
        Execute execute = new Execute();

        Hero hero = new Hero();
        Hero zed = new Zed();
        Hero fizz = new Fizz();

        hero.accept(execute);
        zed.accept(execute);
        fizz.accept(execute);
    }
}

测试结果:

hero
zed
fizz

  在上述示例中,变量调用 accept 方法,是第一次分派,因为使用了方法重写,所以是动态分派。在 accept 方法的具体执行中调用了 execute 对象的 execute 方法,这是第二次分派,因为 execute 方法进行了重载, 但是在 execute 方法的具体入参中使用了 this,即入参是当前对象,也就是变量的实际类型,所以这里的重载也就变成了动态分派了。

  双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载也就是动态的了。

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

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

相关文章

STM32F103VET6基于STM32CubeMX RTC时钟报警中断使用示例

STM32F103VET6基于STM32CubeMX RTC时钟报警中断使用示例&#x1f4fa;STM32CubeMX配置RTC时钟报警中断演示过程&#xff1a; &#x1f4cc;相关篇《STM32F103VET6基于STM32CubeMX RTC时钟秒更新中断使用示例》&#x1f4cc;《STM32F103VET6基于STM32CubeMX RTC时钟使用示例》&a…

VUE-router

七.Vue-router 1、什么是vue-router vue-router是vue.js官方路由管理器。vue的单页应用是基于路由和组件的&#xff0c;路由用于设定访问路径&#xff0c;并将路径和组件映射起来。 传统页面切换是用超链接a标签进行切换。但vue里是用路由&#xff0c;因为我们用Vue做的都是…

K8s 前世今生与架构组件简析

k8s 前世 k8s 的前世是google 内部的Borg 系统&#xff0c;是一个作业调度平台&#xff0c;调度的对象是一个个进程。 Borg 本身也利用了容器化技术比如 Cgroups, Namespace 实现应用的隔离。 运行在线上的业务主要分为在线业务&#xff08;prod&#xff09;和离线业务(non-…

Netty实战与源码剖析(二)——Netty线程模型

1 线程模型基本介绍 不同的线程模式&#xff0c;对于程序的性能有很大的影响&#xff0c;Netty为何具有如此高的性能&#xff0c;很大程度上是得益于Netty采用的线程模型。 目前主流存在的线程模型有两种&#xff1a; 传统阻塞IO模型Reactor模型 然而根据Reactor的数量和处理…

零基础转行程序员,有哪些经验可以借鉴?

随着互联网行业的迅速发展&#xff0c;市场对于程序员的需求增大&#xff0c;越来越多的人开始转行到IT领域&#xff0c;程序员也不再基于计算机科学或软件工程等个别专业的毕业生&#xff0c;其他非计算机相关专业的&#xff0c;像学物理、数学、自动化&#xff0c;甚至英语、…

云原生丨DataX在数据迁移中的应用与实践

文章目录一、前言二、准备工作三、安装工具与数据迁移Demo安装工具与数据迁移三、使用Datax抽取移动云上的gauss数据库四、Datax工具逻辑说明整体框架核心模板介绍流程调度数据库类型插件读、写说明五、Datax工具参数说明SettingReader& writerjdbcUrlusernamepasswordtabl…

数据人PK也无人,为什么业务部门的数据需求都是急活?

**导读&#xff1a;**你是不是经常听到数据开发吐槽业务部门&#xff1a;我可以理解业务部门数据需求多&#xff0c;但为什么经常要得这么急呢&#xff1f; 作为一个数据开发者&#xff0c;可以回想一下&#xff0c;当初是怎么进入数据行业的。 是不是也是听一些大V忽悠&…

Java Swing JSlider:滑块组件

在前面的章节中&#xff0c;我们介绍了 Swing 设计简单界面所需的窗口、布局组件以及如何响应事件。Swing 还提供了很多高级组件&#xff0c;如菜单栏、工具栏、文件选择器、表格以及树等。使用这些高级组件可以实现更为复杂的布局&#xff0c;也可以使程序界面更加人性化&…

颜色分析.

介绍 宽光谱光源对许多光学系统都很重要&#xff0c;应用范围包括白光照明、分光计等。Fred中的颜色图像分析&#xff0c;是通过计算每个像素的色度坐标并在表面上显示生成的RGB值来生成颜色分布。此外&#xff0c;FRED还可以显示彩色色度图&#xff0c;并在用户移动光标时指…

CSS -- CSS选择器精讲(基础选择器,符合选择器,属性选择器,结构伪类选择器,伪选择器)

文章目录1 CSS基础选择器1.1 选择器的分类1.2 标签选择器1.3 类选择器1.4 id选择器1.5 通配符选择器1.6 基础选择器总结2 CSS的复合选择器2.1 什么是复合选择器2.2 后代选择器2.3 子选择器2.4 并集选择器2.5 伪类选择器2.6 链接伪类选择器2.7 :focus 伪类选择器2.8 复合选择器总…

正点原子-Linux嵌入式开发学习-第二期04

第十一讲&#xff1a;BSP工程管理 BSP管理其实就是以前学stm32一样的单独为led写.h和.c&#xff0c;并且文件夹有很多种 使用ubuntu的vscode创建bsp文件 第一步&#xff1a;新建bsp文件夹&#xff0c;在bsp文件夹新建各个外设或者功能的文件夹&#xff08;一定是在相应的文件夹…

nodejs+vue+elementui鲜花销售商城管理系统410

前台&#xff1a; (1) 用户注册&#xff1a;用户名&#xff0c;密码&#xff0c;确认密码&#xff0c;邮箱&#xff0c;手机号&#xff0c;真实姓名&#xff0c;收货地址 (2) 用户登录&#xff1a;用户名&#xff0c;密码&#xff0c;验证码 登录后在主页显示欢迎信息&am…

2022年保险行业和产品研究报告

第一章 行业概况 保险业是经营风险的特殊行业。保险是以契约形式确立双方经济关系&#xff0c;以缴纳保险费建立起来的保险基金&#xff0c;对保险合同规定范围内的灾害事故所造成的损失&#xff0c;进行经济补偿或给付的一种经济形式。 保险是专门以风险为经营对象、为人们提…

RK3568开发环境搭建

前面我给大家展示了RK3568的开发板&#xff0c;但是并没有对RK3568的芯片资源进行描述&#xff0c;这里简单给大家看下该芯片的资源&#xff0c;具体的请看瑞芯微官网https://www.rock-chips.com/ 对芯片有了写了解之后&#xff0c;下面就开始搭建开发环境&#xff0c;让我们早…

Databend 开源周报 #72

Databend 是一款强大的云数仓。专为弹性和高效设计&#xff0c;自由且开源。 即刻体验云服务&#xff1a;https://app.databend.com。 What’s New 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 Features & Improvements Multiple Catalogs …

操作系统:虚拟存储器 练习题(带有答案和解析)

文章目录1.虚拟存储器概述1.1.常规存储管理方式的特征和局部性原理1.2.虚拟存储器的定义和特征1.3.虚拟存储器的实现方法2.请求分页存储管理方式2.1.请求分页中的硬件支持2.2.请求分页中的内存分配3.页面置换算法3.1.最佳置换算法和先进先出置换算法3.2.最近最久未使用和最少使…

Spring事件处理

在实际业务开发中&#xff0c;有时候复杂性的业务之间需要解耦&#xff0c;常用的方法&#xff1a;同步、异步、MQ。但 MQ 重啊&#xff0c;非必要不提升架构复杂度。 针对同步和异步使用方式&#xff1a;&#xff11;.定时器 &#xff12;.Spring Event. Spring Event: 观察者…

网站反爬指南:政府网站篇

目录 前言 黑灰产为何盯上政务网站&#xff1f; 如何反爬&#xff1f; 前言 网络爬虫正在成为政务网站们最大的威胁之一。 随着网络安全被提升到国家层面&#xff0c;网站安全管理和防护日趋重要&#xff0c;政务网站既要确保网站信息的及时和准确&#xff0c;又要能应对网络…

15. Spring事务管理

1. Spring事务简介 事务作用&#xff1a;在数据层保障一系列的数据库操作同成功同失败Spring事务作用&#xff1a;在数据层或**业务层**保障一系列的数据库操作同成功同失败 2. 案例 2.1 需求和分析 需求&#xff1a;实现任意两个账户间转账操作需求微缩&#xff1a;A账户减…

如何使用 SAP OData 服务向 ABAP 服务器上传文件试读版

本教程到目前为止开发的 OData 图书管理服务&#xff0c;可以在 ABAP 系统里对图书数据进行增删改查。 本步骤我们继续介绍如何通过 SAP OData 服务&#xff0c;实现向 ABAP 系统上传文件的需求。我们采取 Postman 进行文件上传。 先看一下通过本文介绍的步骤&#xff0c;实现…