设计模式之访问者模式【行为型模式】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:
想系统/深入学习某技术知识点…
一个人摸索学习很难坚持,想组团高效学习…
想写博客但无从下手,急需写作干货注入能量…
热爱写作,愿意让自己成为更好的人…

文章目录

  • 前言
  • 一、概述
  • 二、结构
  • 三、案例实现
  • 四、优缺点
  • 五、使用场景
  • 六、扩展
  • 总结


前言

一、概述
二、结构
三、案例实现
四、优缺点
五、使用场景
六、扩展


一、概述

定义:

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

二、结构

访问者模式包含以下主要角色:

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

三、案例实现

【例】给宠物喂食

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人、其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗、宠物猫
  • 结构对象角色:主人家

类图如下:
在这里插入图片描述
代码如下:

创建抽象访问者接口

public interface Person {void feed(Cat cat);void feed(Dog dog);
}

创建不同的具体访问者角色(主人和其他人),都需要实现 Person接口

public class Owner implements Person {@Overridepublic void feed(Cat cat) {System.out.println("主人喂食猫");}@Overridepublic void feed(Dog dog) {System.out.println("主人喂食狗");}
}public class Someone implements Person {@Overridepublic void feed(Cat cat) {System.out.println("其他人喂食猫");}@Overridepublic void feed(Dog dog) {System.out.println("其他人喂食狗");}
}

定义抽象节点 – 宠物

public interface Animal {void accept(Person person);
}

定义实现Animal接口的 具体节点(元素)

//具体元素角色类(宠物狗)
public class Dog implements Animal {@Overridepublic void accept(Person person) {person.feed(this);//访问者给宠物狗喂食System.out.println("好好吃,汪汪汪!!!");}
}//具体元素角色类(宠物猫)
public class Cat implements Animal {@Overridepublic void accept(Person person) {person.feed(this);//访问者给宠物猫喂食System.out.println("好好吃,喵喵喵!!!");}
}

定义对象结构,此案例中就是主人的家

public class Home {private List<Animal> nodeList = new ArrayList<Animal>();public void action(Person person) {//遍历元素集合,获取每一个元素,让访问者访问每一个元素for (Animal node : nodeList) {node.accept(person);}}//添加操作public void add(Animal animal) {nodeList.add(animal);}
}

测试类

public class Client {public static void main(String[] args) {//创建Home对象Home home = new Home();//添加元素到Home对象中home.add(new Dog());home.add(new Cat());//创建主人对象Owner owner = new Owner();//让主人喂食所有的宠物home.action(owner);//创建其他人对象Someone someone = new Someone();//让其他人喂食所有的宠物home.action(someone);}
}

在这里插入图片描述

四、优缺点

1,优点:

  • 扩展性好

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

  • 复用性好

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

  • 分离无关行为

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

2,缺点:

  • 对象结构变化很困难

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

  • 违反了依赖倒置原则

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

五、使用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。

  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

六、扩展

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

1,分派:

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

静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。

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

2,动态分派:

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

public class Animal {public void execute() {System.out.println("Animal");}
}public class Dog extends Animal {@Overridepublic void execute() {System.out.println("dog");}
}public class Cat extends Animal {@Overridepublic void execute() {System.out.println("cat");}
}public class Client {public static void main(String[] args) {Animal a = new Dog();a.execute();Animal a1 = new Cat();a1.execute();}
}

上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。

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

3,静态分派:

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

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);}
}

运行结果:

在这里插入图片描述
这个结果可能出乎一些人的意料了,为什么呢?

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

4,双分派:

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

public class Animal {public void accept(Execute exe) {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();a.accept(exe);d.accept(exe);c.accept(exe);}
}

在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。

说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。

运行结果如下:
在这里插入图片描述
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。


总结

以上就是设计模式之访问者模式【行为型模式】的相关知识点,希望对你有所帮助。
积跬步以至千里,积怠惰以至深渊。时代在这跟着你一起努力哦!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推荐两款好用的卫星地图。

问题描述&#xff1a;推荐两款好用的卫星地图。 问题解决&#xff1a;谷歌地球、高德卫星地图。个人感觉谷歌地球好用一些。

金蝶云星空和吉客云单据接口对接

金蝶云星空和吉客云单据接口对接 对接系统&#xff1a;吉客云 吉客云是基于“网店管家”十五年电商ERP行业和技术积累基础上顺应产业发展需求&#xff0c;重新定位、全新设计推出的换代产品&#xff0c;从业务数字化和组织数字化两个方向出发&#xff0c;以构建流程的闭环为依归…

基于卡尔曼滤波的声源跟踪方法研究

基于卡尔曼滤波的声源跟踪方法研究 摘 要一、研究意义二、研究内容三、算法介绍3.1基于到达时间差的定位算法3.1.1算法原理介绍3.1.2仿真实验设计与分析 3.2扩展卡尔曼滤波算法3.2.1算法的基本原理3.2.2仿真实验及分析 3.3无迹卡尔曼滤波算法3.3.1算法的基本原理3.3.2仿真实验及…

web学习笔记(十五)

目录 1.Date对象 1.1日期对象的概念 1.2Date()方法的使用 1.3Date()常用方法汇总 1.4例题&#xff1a;用函数编写一个倒计时 2.函数 2.1函数的概念 2.2函数的使用 2.3函数的参数 2.4函数的声明 2.5函数的返回值 2.6异步函数 3特殊函数类型 3.1匿名函数 3.2箭头函数…

精细微调技术在大型预训练模型优化中的应用

目录 前言1 Delta微调简介2 参数微调的有效性2.1 通用知识的激发2.2 高效的优化手段3 Delta微调的类别3.1 增量式微调3.2 指定式微调3.3 重参数化方法 4 统一不同微调方法4.1 整合多种微调方法4.2 动态调整微调策略4.3 超参数搜索和优化 结语 前言 随着大型预训练模型在自然语…

基于cy7c68013的逻辑分析仪nanoDLA全套软件linux下编译测试

0. 环境 - win10 - ubuntu22 - nanoDLA 提前获取到源码&#xff1a;-> 浏览器打开 https://github.com/wuxx/nanoDLA -> Download as zip. 硬件就直接用taobao买到的&#xff0c;原理图是 1. win10出厂测试 1.1 安装pulseview nanoDLA-master\software\pulseview-0.4.…

VGAN实现视网膜图像血管分割(基于pytorch)

背景介绍 VGAN&#xff08;Retinal Vessel Segmentation in Fundoscopic Images with Generative Adversarial Networks&#xff09;出自2018年的一篇论文&#xff0c;尝试使用生成性对抗网络实现视网膜血管分割的任务,原论文地址&#xff1a;https://arxiv.org/abs/1706.0931…

【K8s学习】

k8s的简单执行流程&#xff1a; Kubernetes Master&#xff08;API Server、Scheduler等组件&#xff09;负责调度Pod到合适的Node上。 当Pod被调度到某个Node时&#xff0c;该Node上的kubelet代理会收到指令并开始执行Pod的生命周期管理任务&#xff0c;包括创建、监控和终止P…

React16源码: React中的schedule调度整体流程

schedule调度的整体流程 React Fiber Scheduler 是 react16 最核心的一部分&#xff0c;这块在 react-reconciler 这个包中这个包的核心是 fiber reconciler&#xff0c;也即是 fiber 结构fiber的结构帮助我们把react整个树的应用&#xff0c;更新的流程&#xff0c;能够拆成每…

c++学习:智能指针的底层作用原理+用法

目录 智能指针作用原理 作用 原理 模仿int*类型的智能指针 模仿所有类型的智能指针&#xff08;模板&#xff09; 共享智能指针类 思考&#xff1b;如果多个智能指针同时指向同一个堆空间&#xff0c;怎么只执行一次析构函数进行释放空间 &#xff08;共享智能指针类&…

ubuntu 20.04下 Tesla P100加速卡使用

1.系统环境&#xff1a;系统ubuntu 20.04, python 3.8 2.查看cuDNN/CUDA与tensorflow的版本关系如下&#xff1a; Build from source | TensorFlow 从上图可以看出&#xff0c;python3.8 对应的tensorflow/cuDNN/CUDA版本。 3.安装tensorflow #pip3 install tensorflow 新版…

MES生产执行系统在生产车间的主要作用

MES生产执行系统提供从生产订单下达到产品完成全流程的优化管理。实现现场设备、执行系统及管理系统的集成&#xff0c;实时监控生产管理各项绩效指标。 如果说ERP是上层决策&#xff0c;生产车间是下层执行&#xff0c;那么MES就是连接管理软件和一线生产的中间桥梁。 MES也…