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

这篇具有很好参考价值的文章主要介绍了第二十一章行为性模式—访问者模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


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

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

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

行为型模式分为:

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

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

访问者模式

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

解决的问题

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

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

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

结构

  • 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,就是具体的实际类型的对象,所以是静态分派。

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

理解:文章来源地址https://www.toymoban.com/news/detail-468104.html

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

到了这里,关于第二十一章行为性模式—访问者模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第二十一章

    计算机应用实现了多台计算机间的互联,使得它们彼此之间能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序,这些程序借助于网络协议,相互之间可以交换数据。编写网络应用程序前,首先必须明确所要使用的网络协议。TCP/IP协议是网络应用程序的

    2024年02月04日
    浏览(51)
  • 第二十一章 Unity 光源

    光源是每个场景必不可少的部分,光源除了能够照亮场景之外,还可以产生阴影效果。 Unity中分为四种 光源类型 : 1. 方向光:Directional Light 用于模拟太阳光,方向光任何地方都能照射到。 2. 点光源:Point Light 用于模拟电灯泡的照射效果。 3. 聚光灯:Spot Light 用于模拟聚光灯

    2024年02月16日
    浏览(46)
  • 第二十一章网络通信

    网络程序设计基础 局域网与互联网 为了实现两台计算机的通信,必须用一个网络线路连接两台计算机。如下图所示  网络协议 1.IP协议 IP是Internet Protocol的简称,是一种网络协议。Internet 网络采用的协议是TCP/IP协议,其全称是Transmission Control Protocol/Internet Protocol。Internet 依靠

    2024年02月05日
    浏览(37)
  • 【Three.js】第二十一章 Physics 物理

    物理是WebGL可以添加到项目体验中最酷的功能之一。人们喜欢真实物理感的物体,看到它们碰撞、倒塌、坠落和弹跳,就像我的作品集一样: https: //bruno-simon.com/ 有很多方法可以将物理功能添加到您的项目中,这取决于您想要实现的目标。您可以使用一些数学和解决方案(例

    2024年02月09日
    浏览(36)
  • 第二十一章 : Spring Boot 集成RabbitMQ(五)

    第二十一章 : Spring Boot 集成RabbitMQ(五) 前言 本章知识点: 如何保证消息100%可靠性发送的技术解决方案。 一、 应用场景 在使用消息队列时,因为生产者和消费者不直接交互,所以面临下面几个问题: 1)要把消息添加到队列中,怎么保证消息成功添加? 2)如何保证消息

    2024年02月03日
    浏览(42)
  • UCB Data100:数据科学的原理和技巧:第二十一章到第二十六章

    原文:SQL II 译者:飞龙 协议:CC BY-NC-SA 4.0 学习成果 介绍过滤组的能力 在 SQL 中执行数据清理和文本操作 跨表连接数据 在本讲座中,我们将继续上次的工作,介绍一些高级的 SQL 语法。 首先,让我们加载上一堂课的数据库。 HAVING 通过在每个组的所有行上应用一些条件来过

    2024年01月21日
    浏览(178)
  • 第二十一章 Prim算法与Kruskal算法(通俗证明与详细讲解)

    我们先解释一下什么是最小生成树。 这个概念是基于图的,如果说存在一条路线串通起来了所有的点,那么这条路线就叫做生成树。而在这些路线中最短的那一条就叫做最小生成树。 如上图所示,图中的红色路线就是一个生成树,假设这条红色路线是众多生成树路线中最小

    2024年02月11日
    浏览(34)
  • Chrome 开发者工具 第二十一章(替换 Web 内容和 HTTP 响应)

    Chrome 开发者工具的本地替换功能是一个强大的工具,它允许开发者在不修改服务器代码的情况下模拟前端更改。这个功能特别适用于那些需要快速测试前端更改,但又不想或不能等待后端更新的情况。 本地替换的工作原理 本地替换通过在开发者工具中进行更改,并将这些更

    2024年02月22日
    浏览(55)
  • 《HeadFirst设计模式(第二版)》第十一章代码——代理模式

    代码文件目录:  RMI: MyRemote MyRemoteClient MyRemoteImpl 能够远程监控的糖果机: 在上一章的代码的基础上做一些修改 GumballMachine GumballMachineRemote GumballMachineTestDrive GumballMonitor GumballMonitorTestDrive 五个状态类: 同样的修改:

    2024年02月12日
    浏览(38)
  • 第二十一章:CCNet:Criss-Cross Attention for Semantic Segmentation ——用于语义分割的交叉注意力

    原文题目:《CCNet:Criss-Cross Attention for Semantic Segmentation 》 原文引用:Huang Z, Wang X, Huang L, et al. Ccnet: Criss-cross attention for semantic segmentation[C]//Proceedings of the IEEE/CVF international conference on computer vision. 2019: 603-612. 原文链接: https://openaccess.thecvf.com/content_ICCV_2019/papers/Huang_CCNet_Criss

    2024年02月16日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包