第二十一章行为性模式—访问者模式

news2024/11/13 14:47:14

文章目录

  • 访问者模式
    • 解决的问题
    • 结构
    • 实例
    • 存在的问题
    • 使用场景
  • 拓展
    • 动态分派
    • 静态分派
    • 双分派

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式:

  • 类行为模式:采用继承机制来在类间分派行为

  • 对象行为模式:采用组合或聚合在对象间分配行为

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

行为型模式分为:

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 职责链模式
  • 状态模式
  • 观察者模式
  • 中介者模式
  • 迭代器模式
  • 访问者模式
  • 备忘录模式
  • 解释器模式

以上 11 种行为型模式,除了模板方法模式解释器模式是类行为型模式,其他的全部属于对象行为型模式。

访问者模式

访问者模式:访问者模式,用于封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作

解决的问题

在之前有一则新闻,一个爷爷给孙子买学习机器人,本以为是一个智能机器人,但是买回来之后发现是一个1+1=1的傻蛋机器人,爷爷就找到经销商来质问,经销商说哦,不讲武德,这个机器人的系统版本太低,需要系统版本升级

image-20230531114641645

  • 这个软件升级,其机器人的硬件是不会变的,这个软件包中包含新的指令,使机器人能完成更多功能,之所以这个升级包能完成升级的原因是因为这个系统升级版能够访问这个机器人中的硬件,然后利用这些硬件资源去完成新的指令

结构

  • Visitor:抽象访问者角色,定义了对每一个元素 Element 访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element 的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • ConcreteVisitor:具体访问者角色,给出对每一个元素类访问时所产生的具体行为。
  • Element:抽象元素角色,定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • ConcreteElement:具体元素角色, 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • Object Structure:对象结构角色,定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

实例

机器人版本升级案例

抽象元素角色

public abstract class Hardware {
    //指令
    String common;

    public Hardware(String common) {
        this.common = common;
    }
    public void run(){
        System.out.println(common);
    }
    public abstract void accept();
}

具体元素角色

public class CPU extends Hardware{
    public CPU(String common) {
        super(common);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visitCPU(this);
    }
}
public class HardDisk extends Hardware{
    public HardDisk(String common) {
        super(common);
    }

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

抽象访问者角色

public interface Visitor {
    void visitCPU(CPU cpu);
    void visitHardDisk(HardDisk hardDisk);
}

具体访问者角色

public class UpdateVisitor implements Visitor {
    @Override
    public void visitCPU(CPU cpu) {
        cpu.common+=": 1+1=2";
    }

    @Override
    public void visitHardDisk(HardDisk hardDisk) {
        hardDisk.common+=": 记住: 1+1=2";
    }
}

对象结构角色

public class EggRobot {
    private HardDisk disk;
    private CPU cpu;

    public EggRobot() {
        this.disk = new HardDisk("记住 1+1=1");
        this.cpu = new CPU("1+1=1");
    }
    public void calc(){
        cpu.run();
        disk.run();
    }
    public void accept(Visitor visitor){
        cpu.accept(visitor);
        disk.accept(visitor);
    }
}

访问者模式:访问者模式,用于封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作

  • 某种数据结构指的就是对象结构角色也就是我们的机器人蠢蛋
  • 各元素指的就是具体元素角色,也就是我们的机器人的各个硬件比如CPU,硬盘
  • 我们定义了访问者类,封装了新的操作,不需要改变数据结构,通过具体元素角色(硬件)暴露方法,不改变数据结构(机器人)的情况下,让其访问数据结构中的具体元素,来实现新的操作

存在的问题

优点:

扩展性好

  • 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

复用性好

  • 通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

分离无关行为

  • 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

缺点:

对象结构变化很困难

  • 在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了 “开闭原则”。

违反了依赖倒置原则

  • 访问者模式依赖了具体类,而没有依赖抽象类。

使用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

拓展

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

分派:变量被声明时的类型叫做变量的静态类型(也被叫做明显类型);而变量所引用的对象的真实类型又叫做变量的实际类型。

  • 比如 Map map = new HashMap() ,map 变量的静态类型是 Map ,实际类型是 HashMap 。

根据对象的类型而对方法进行的选择,就是分派 (Dispatch),分派又分为两种:

  • 动态分派 (Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法,Java 通过方法的重写支持动态分派。

  • 静态分派 (Static Dispatch) 发生在编译时期,分派根据静态类型信息发生,方法重载就是静态分派。

动态分派

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

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

public class Dog extends Animal {
    @Override
    public void execute() {
        System.out.println("dog");
    }
}

public class Cat extends Animal {
     @Override
    public void execute() {
        System.out.println("cat");
    }
}

public class Client {
   	public static void main(String[] args) {
        Animal a = new Animal();
        a.execute();
      

    Animal d = new Dog();
    d.execute();
    
    Animal c = new Cat();
    c.execute();
}

}
animal
dog
cat

上面的代码就是简单的多态

Java 编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。

静态分派

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

public class Animal {}

public class Dog extends Animal {}

public class Cat extends Animal {}

public class Execute {
    public void execute(Animal a) {
        System.out.println("Animal");
    }
    public void execute(Dog d) {
        System.out.println("dog");
    }
    public void execute(Cat c) {
        System.out.println("cat");
    }
}

public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal a1 = new Dog();
        Animal a2 = new Cat();
				// 静态分派根据静态类型决定
        Execute exe = new Execute();
        exe.execute(a);
        exe.execute(a1);
        exe.execute(a2);
    }
}

运行结果:

animal
animal
animal

这个结果可能出乎一些人的意料了,为什么呢?

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。

双分派

所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者的运行时区别,还要根据参数的运行时区别。

public class Animal {
    public void accept(Execute exe) {
        // 2.传递了this给Execute,根据静态类型进行方法重载实现静态分派
        exe.execute(this);
    }
}

public class Dog extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Cat extends Animal {
    public void accept(Execute exe) {
        exe.execute(this);
    }
}

public class Execute {
    public void execute(Animal a) {
        System.out.println("animal");
    }
    public void execute(Dog d) {
        System.out.println("dog");
    }
    public void execute(Cat c) {
        System.out.println("cat");
    }
}

public class Client {
    public static void main(String[] args) {
        Animal a = new Animal();
        Animal d = new Dog();
        Animal c = new Cat();Execute exe = new Execute();
  	// 1.exe传递给Animal类型的变量调用,方法重写实现动态分派
​    a.accept(exe);
​    d.accept(exe);
​    c.accept(exe);
}

}
animal
dog
cat

上面代码中:

  • 客户端将 Execute 的对象做为参数传递给 Animal 类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法;

  • 同时也将自己 this 作为参数传递进去,这里就完成了第二次分派,这里的 Execute 类中有多个重载方法,而传递进行的是 this,就是具体的实际类型的对象,所以是静态分派。

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

理解:

  • 1、动态分派要达到的效果是正确的,分别输出 a、b、c,但是实现每个对象的方法都写在其各自的类中,不好维护,它的实现过程不行。
  • 2、静态分派的这种实现过程是比较好的,没有把输出 a、b、c 的操作耦合到各自的类中,而是统一在 Execute 中维护执行,但是却没有达到想要的效果。。
  • 3、所以也就有了双分派,同样是将执行过程放在 Execute 中管理,最后也达到了想要的效果,输出结果为 a、b、c。其实现方法其实就是在各个类中依赖了 Execute,再将自己 this 传递过去,从而实现在 Execute 操作的目的。

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

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

相关文章

华为OD机试真题B卷 Java 实现【箱子之形摆放】,附详细解题思路

一、题目描述 要求将一批箱子按从上到下以‘之’字形的顺序摆放在宽度为 n 的空地上,输出箱子的摆放位置,例如:箱子ABCDEFG,空地宽为3。 摆放效果如下图: 则输出结果为: AFG BE CD 二、输入描述 一行…

智能语音交互流程

引言 用该文来讲解语音全流程涉及到的技术,整体语音涉及的交互流程如下图: Part1 唤醒 语音唤醒指的是通过预设的关键词即可将智能硬件从休眠状态唤醒,来执行相应操作。 1.1 交互模式 传统模式 唤醒方式:先唤醒设备&#xff…

重学数据结构与算法

学习数据结构与算法的目的: 优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度 教程总纲: 暴力解法(模拟)、算法优化(递归/二分/排序/DP)、时刻转换(数据结构) 1.时间复杂度的核心方法论2.增删查——选取数据结构的基…

MySQL_9 事务机制与隔离机制

目录 一、事务概述 1.定义 : 2.事务和锁 : 二、事务操作 1.MySQL控制台事务的基本操作 : 2.代码演示 : 3.注意事项 : 三、事务的“ACID”特性 : 四、隔离机制 1.介绍 : 2.分类 : 3.常用指令 : 一、事务概述 1.定义 : 事务用于保证数据的一致性,它由一…

python批量实现pdf转换为图片|实现pdf转为jpg/png|实现word批量转pdf|python批量实现word转换为图片

本文介绍了一种使用 Python 脚本来完成这项工作的方法,该脚本基于 PyMuPDF、pdf2image 和 win32com 库实现,可以帮助您快速地将 Word 文档转换为 PDF 文件,并将 PDF 文件转换为 PNG 图片。 一、安装所需的库和软件 在开始使用该脚本之前&am…

AWTK实现汽车仪表Cluster/DashBoard嵌入式GUI开发(一):概述

AWTK=Toolkit AnyWhere,一款国产免费开源工具,ZLG开发的开源GUI引擎,为嵌入式等系统提供图形界面开发IDE。 随着汽车技术的发展,汽车仪表盘也在快速发展,从最初的机械式到电气式,再到数字化。这次电动化、智能化又一次让汽车仪表出现了飞跃式的发展,再未来,仪表板上可…

04SpringCloud 消息中间件

为什么要使用消息中间件 同步通信:耗时长,受网络波动影响,不能保证高成功率,耦合性高。 同步,异步 并发:一段时间(1S)多个请求数 并行:时间节点,多个指令…

干接点与湿接点

(1)干接点的定义 无源开关;具有闭合和断开的2种状态;2个接点之间没有极性,可以互换。 常见的干接点信号有: 1)各种开关如:限位开关、行程开关、脚踏开关、旋转开关、温度开关、液…

超越象限:解密 α 碎片的归属问题

文章目录 参考环境α 碎片的归属问题问题概述终边相同角圆心角终边相同角 象限角 描述象限角第一象限角任意象限角 特殊方案问解 叠加坐标系上部下部叠加坐标系 一般方案问解任意角 α分析绘图要领叠加坐标系N-N 参考 项目描述搜索引擎Google 、Bing百度百科首页佟大大还是ETT…

苹果iPhone14如何批量删除联系人?iPhone 14批量删除联系人方法

苹果iPhone 14如何批量删除联系人?手动一个个删实在太麻烦了! 苹果iPhone 14手机的通讯录本身不支持批量删除联系人,但是如果通讯录在iCloud中开启过备份,就可以对联系人进行批量操作。 需要注意的是,iPhone 14手机端…

聊聊开源的类ChatGPT工作——ChatGLM

这是”聊聊开源的类ChatGPT工作“的第二篇,写第一篇[7]的时候,当时恰巧MOSS开源,就顺手写了MOSS。但要问目前中文领域的“开源”的语言模型谁更强,公认的还是ChatGLM-6B(以下简称ChatGLM)。 下面是官方对C…

【20230531】Git命令和Github相关使用

1 在Git中缓存GitHub凭据 根据系统下载对应的GitHub CLI 2.25.1 2. github主页,点击用户头像选择settings->Developer Settings->Personal access tokens申请密钥(主要分为Fine-grained tokens和Tokens classic)gh需要使用的是Tokens …

Spring Boot如何实现接口文档自动生成

Spring Boot如何实现接口文档自动生成 在开发Web应用程序时,接口文档是非常重要的一环,它可以帮助我们快速了解API的功能和使用方法,同时也是与其他开发人员和团队协作的重要工具。然而,手动编写和维护接口文档是一项繁琐的工作&…

商品上架业务

一.商品上架操作 将检索数据存入es,更改商品上架状态为已上架 二.业务设计 (1)设计检索数据 分析:商品上架在 es 中是存 sku 还是 spu? 1)、检索的时候输入名字,是需要按照 sku 的 title 进行…

给大龄准备转行网络工程师的朋友一些建议

我一直认为,网络工程师是一个看能力而不是看年龄的工作。 大龄转行网络工程师到底有没有机会?很多三十多岁的朋友对于跨行业完全心里没底,冒然转行学习网工又不知道从何学起。今天就给大家整理几个在学习网络工程师的时候需要注意的地方&…

Linux系统下imx6ull QT编程—— C++数据封装与数据抽象(八)

Linux QT编程 文章目录 Linux QT编程前言一、数据封装二、数据抽象 前言 封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数…

用JavaScript做一个拼图游戏

喜欢的可以复制下面完整代码查看效果在自己本地查看效果 实现难度&#xff1a;不算大&#xff0c;毕竟是小游戏 开发工具&#xff1a;html&#xff0c;css&#xff0c;js&#xff0c;jquery 效果截图 完整代码 <!DOCTYPE html> <html lang"en"> <he…

24届秋招专场·双指针巧解链表套路题

你好&#xff0c;我是安然无虞。 文章目录 合并两个有序链表分隔链表合并K个有序链表链表中倒数最后K个节点变形题: 删除链表的倒数第N个节点链表的中点判断链表是否有环环形链表II相交链表 大家好, 好久不见了, 从今天开始, 后面会经常更新笔试面试真题, 准备今年24届秋招的小…

Centos7安装Kubernetes 1.27.2

目录 一、准备工作 二、容器运行时 三、安装kubelet 、kubeadm、 kubectl 四、配置CNI 五、安装nginx 一、准备工作 1、更新yum源安装 vim、net-tools等工具&#xff08;每个节点都执行&#xff09; yum update -yyum install vim -yyum install net-tools -y 2、配置每…

简单移位器结构介绍

移位器 一位可控移位器 其实是一个复杂的多路开关电路&#xff0c;根据不同控制信号&#xff0c;将输入左移或右移或不变。多位的移位可以简单串联这样的单元实现&#xff0c;但移位位数多时&#xff0c;该方法过于复杂&#xff0c;不实用并且速度很慢。 桶形移位器 由晶体管…