Java设计模式之访问者模式
作者:tianClassmate 发布时间:2023-04-17 20:00:15
大多数情况下你不需要访问者模式,但当一旦需要访问者模式时,那就是真的需要它了,这是设计模式创始人的原话。可以看出应用场景比较少,但需要它的时候是不可或缺的,这篇文章就开始学习最后一个设计模式——访问者模式。
一、概念理解
访问者模式概念:封装作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
通俗的解释就是,系统中有一些固定结构的对象(元素),在其内部提供一个accept()方法用来接受访问者对象的访问,不同的访问者对同一元素的访问内容不同,所以使得相同的元素可以产生不同的元素结果。
比如在一个人事管理系统中,有多个工种的员工和多个老板,不同的老板对同一个员工的关注点是不同的,CTO可能关注的就是技术,CEO可能更注重绩效。
员工就是一个稳定的元素,老板就是变化的,对应概念就是:封装员工的一些操作,可以在不改变员工类的前提下,增加新的老板访问同一个员工。
在访问者模式中包含五个角色,抽象元素、具体元素、抽象访问者、具体访问者、结构元素。
抽象元素:定义一个接受访问的方法accept,参数为访问者对象。
具体元素:提供接受访问者访问的具体实现调用访问者的访问visit,并定义额外的数据操作方法。
抽象访问者:这个角色主要是定义对具体元素的访问方法visit,理论上来说方法数等于元素(固定类型的对象,也就是被访问者)个数。
具体访问者:实现对具体元素的访问visit方法,参数就是具体元素。
结构对象:创建一个数组用来维护元素,并提供一个方法访问所有的元素。
二、案例实现
在一个公司有干活的工程师和管理者,也有抓技术的CTO和管绩效的CEO,CTO和CEO都会访问管理员和工程师,当公司来了新的老板,只需要增加访问者即可。
工程师和管理者就是元素、公司就是结构体、CEO、CTO就是访问者。
抽象元素:
/**
* 员工 抽象元素 被访问者
* @author tcy
* @Date 29-09-2022
*/
public interface ElementAbstract {
void accept(VisitorAbstract visitor);
}
具体元素-工程师:
/**
* 工程师 具体元素 被访问者
* @author tcy
* @Date 29-09-2022
*/
public class ElementEngineer implements ElementAbstract {
private String name;
private int kpi;
ElementEngineer(String name){
this.name = name;
this.kpi = new Random().nextInt(10);
}
public String getName() {
return name;
}
public int getKpi() {
return kpi;
}
@Override
public void accept(VisitorAbstract visitor) {
visitor.visit(this);
}
public int getCodeLineTotal(){
return this.kpi * 1000000;
}
}
具体元素-管理者:
/**
* 管理者 具体元素 被访问者
* @author tcy
* @Date 29-09-2022
*/
public class ElementManager implements ElementAbstract {
private String name;
private int kpi;
ElementManager(String name){
this.name = name;
this.kpi = new Random().nextInt(10);
}
public String getName() {
return name;
}
public int getKpi() {
return kpi;
}
@Override
public void accept(VisitorAbstract visitor) {
visitor.visit(this);
}
public int getProductNum(){
return this.kpi * 10;
}
}
抽象访问者:
/**
* 抽象访问者
* @author tcy
* @Date 29-09-2022
*/
public interface VisitorAbstract {
void visit(ElementEngineer engineer);
void visit(ElementManager manager);
}
具体访问者-CEO
/**
* 具体访问者CEO
* @author tcy
* @Date 29-09-2022
*/
public class VisitorCEO implements VisitorAbstract {
@Override
public void visit(ElementEngineer engineer) {
System.out.println("工程师:" + engineer.getName() + "KPI:" + engineer.getKpi());
}
@Override
public void visit(ElementManager manager) {
System.out.println("经理:" + manager.getName() + "KPI:" + manager.getKpi() + " 今年共完成项目:" + manager.getProductNum() + "个");
}
}
具体访问者-CTO
/**
* 具体访问者CTO
* @author tcy
* @Date 29-09-2022
*/
public class VisitorCTO implements VisitorAbstract {
@Override
public void visit(ElementEngineer engineer) {
System.out.println("工程师:" + engineer.getName() + " 今年代码量" + engineer.getCodeLineTotal() + "行");
}
@Override
public void visit(ElementManager manager) {
System.out.println("经理:" + manager.getName() + " 今年共完成项目:" + manager.getProductNum() + "个");
}
}
结构体:
/**
* 结构对象
* @author tcy
* @Date 29-09-2022
*/
public class Structure {
List<ElementAbstract> list = new ArrayList<>();
public Structure addEmployee(ElementAbstract employee){
list.add(employee);
return this;
}
public void report(VisitorAbstract visitor){
list.forEach(employee -> {
employee.accept(visitor);
});
}
}
客户端:
/**
* @author tcy
* @Date 29-09-2022
*/
public class Client {
public static void main(String[] args) {
//元素对象
ElementEngineer engineerZ = new ElementEngineer("小张");
ElementEngineer engineerW = new ElementEngineer("小王");
ElementEngineer engineerL = new ElementEngineer("小李");
ElementManager managerZ = new ElementManager("张总");
ElementManager managerW = new ElementManager("王总");
ElementManager managerL = new ElementManager("李总");
//结构体对象
Structure structure = new Structure();
structure.addEmployee(engineerZ).addEmployee(engineerW).addEmployee(engineerL).addEmployee(managerZ).addEmployee(managerW).addEmployee(managerL);
structure.report(new VisitorCTO());
System.out.println("---------------------------------------");
structure.report(new VisitorCEO());
}
}
访问者不愧是最难的设计模式,方法间的调用错综复杂,日常开发的使用频率很低,很多程序员宁可代码写的麻烦一点也不用这种设计模式,但是作为学习者就要学习各种设计模式了。
三、访问者模式在JDk中的应用
JDK的NIO中的 FileVisitor 接口采用的就是访问者模式。
在早期的 Java 版本中,如果要对指定目录下的文件进行遍历,必须用递归的方式来实现,这种方法复杂且灵活性不高。
Java 7 版本后,Files 类提供了 walkFileTree() 方法,该方法可以很容易的对目录下的所有文件进行遍历,需要 Path、FileVisitor 两个参数。其中,Path 是要遍历文件的路径,FileVisitor 则可以看成一个文件访问器,源码如下。
FileVisitor 主要提供了 4 个方法,且返回结果的都是 FileVisitResult 对象值,用于决定当前操作完成后接下来该如何处理。FileVisitResult 是一个枚举类,代表返回之后的一些后续操作,源码如下。
FileVisitResult 主要包含 4 个常见的操作。
FileVisitResult.CONTINUE:这个访问结果表示当前的遍历过程将会继续。
FileVisitResult.SKIP_SIBLINGS:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前文件/目录的兄弟节点。
FileVisitResult.SKIP_SUBTREE:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前目录下的所有节点。
FileVisitResult.TERMINATE:这个访问结果表示当前的遍历过程将会停止。
通过访问者去遍历文件树会比较方便,比如查找文件夹内符合某个条件的文件或者某一天内所创建的文件,这个类中都提供了相对应的方法。它的实现也非常简单,代码如下。
在JDK的应用中我们提供的文件就看做是一个稳定元素,对应访问者模式中的抽象元素;而Files.walkFileTree()方法中的FileVisitor 参数就可看做是角色中的访问者。
四、访问者模式中的伪动态双分派
访问者模式中有一个重要的概念叫:伪动态双分派。
我们一步一步解读它的含义,什么叫分派?根据对象的类型而对方法进行的选择,就是分派(Dispatch)。
发生在编译时的分派叫静态分派,例如重载(overload),发生在运行时的分派叫动态分派,例如重写(overwrite)。
其中分派又分为单分派和多分派。
单分派:依据单个变量进行方法的选择就叫单分派,Java 动态分派(重写)只根据方法的接收者一个变量进行分配,所以其是单分派。
多分派:依据多个变量进行方法的选择就叫多分派,Java 静态分派(重载)要根据方法的接收者与参数这两个变量进行分配,所以其是多分派。
理解了概念我们接着看我们的案例:
@Override
public void accept(VisitorAbstract visitor) {
visitor.visit(this);
}
我们案例中的accept方法,是由元素的运行时类型决定的,应该是属于动态单分派。
我们接着看 visitor.visit(this)又是一次动态单分派,两次动态单分派实现了双分派的效果,所以称为伪动态双分派。
这个概念理解就好,实际应用中知不知道这玩意都不影响。
五、总结
当你有个类,里面的包含各种类型的元素,这个类结构比较稳定,不会经常增删不同类型的元素。而需要经常给这些元素添加新的操作的时候,考虑使用此设计模式。
适用对象结构比较稳定每增加一个元素访问者都要大变动,但加新的操作很简单。集中相关的操作、分离无关的操作。
缺点只有两个字-复杂,号称是最复杂的设计模式。
来源:https://www.cnblogs.com/tianClassmate/p/16743947.html


猜你喜欢
- 问题提出:自己在做一个小网站充当练手,但是前端图片经过base64加密后传往后端在解码。但是一直都有问题,请大神赐教 publi
- 一、合并和拆分PDF文件的方式 PDF文件使用了工业标准的压缩算法,易于传输与储存。它还是页独立的,一个
- 前言大家好,我是bigsai,在数据结构与算法中,二叉树无论是考研、笔试都是非常高频的考点内容,在二叉树中,二叉树的遍历又是非常重要的知识点
- 引言 在一些项目中或是一些特殊的业务场景中,需要用到显示系统的当前时间,以及一些
- 根据数据库表名生成实体类公司用的jpa,没有用mybatis。所以也没有用mybatis自动生成。但有些数据库表字段太多,就想着一劳永逸了,
- HashMap和HashTable,这二者的区别经常被别人问起,今天在此总结一下。(一)继承的历史不同public class
- 关于Redis的概念和应用本文就不再详解了,说一下怎么在java应用中设置过期时间。在应用中我们会需要使用redis设置过期时间,比如单点登
- java实现在线预览- -之poi实现word、excel、ppt转html,具体内容如下所示:###简介java实现在线预览功能是一个大家
- 首先对图片进行UUID 防止图片被覆盖以及爬图UUID的生成规则:日期时间,MAC地址,HashCode,随机数(多种之一)开发上传接口,两
- 今天和大家聊一聊Android中关于FontMetrics的几个属性的理解,在Android中用画笔绘制文字时,文字最终的大小是和绘制文字的
- 本文实例讲述了Android编程实现添加低电流提醒功能的方法。分享给大家供大家参考,具体如下:特殊需求,检测电流是否正常。监听如下广播:In
- 1、public String(char[] c,begin,length).从字符数组c的下标begin处开始,将长度为length的字符
- 一.总体设计1.寻找规律,公式化的生成坐标系。2.将生成坐标系的关键参数设置为可自定义,从而可变的可以生成自己想要的坐标系。3.将需要绘制的
- @RequestParam@RequestParam:接收来自RequestHeader中,即请求头。通常用于GET请求,例如:http:/
- 0、引言在开发的过程中,很多业务场景需要一个树形结构的结果集进行前端展示,也可以理解为是一个无限父子结构,常见的有报表指标结构、菜单结构等。
- 对象重复是指对象里面的变量的值都相等,并不定是地址。list集合存储的类型是基础类型还比较好办,直接把list集合转换成set集合就会自动去
- java中字符串转整数及MyAtoi方法的实现 该题虽然和我们正常使
- 本文实例讲述了C#使用xsd文件验证XML格式是否正确的实现方法。分享给大家供大家参考,具体如下://创建xmlDocumentXmlDoc
- 最近做的一个项目涉及到文件上传与下载。前端上传采用百度webUploader插件。有关该插件的使用方法还在研究中,日后整理再记录。本文主要介
- 前言目前有两套RocketMQ集群,集群A包含topic名称为cluster_A_topic,集群B包含topic名称为cluster_B_