欢迎来到智品网!

快讯

大白话聊访问者模式:从入门到实践



发布时间:2021-02-18 18:56:31   来源:互联网   作者:

访问者模式,重点在于访问者二字。说到访问,我们脑海中必定会想起新闻访谈,两个人面对面坐在一起。从字面上的意思理解:其实就相当于被访问者(某个公众人物)把访问者(记者)当成了外人,不想你随便动。你想要什么,我弄好之后给你(调用你的方法)。

访问者模式的定义如下所示,说的是在不改变数据结构的提前下,定义新操作。

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

---开始聊工作经历---

Objects.requireNonNull(file);

Element具体元素。这里指的是具体被访问的类,在我们这个例子中指的是Scientist类。一般情况下,我们会提供一个accept()方法,接收访问者参数,将相当于接受其范文申请。但这个方法也不是必须的,只要你能够拿到visitor对象,你怎么定义这个参数传递都可以。

首先,我们需要有一个Visitor类,这里定义了一些外部(记者)可以做的事情(提学校经历、工作经历、科研成就的问题)。

没错,这也是正确答案!

publicinterfaceVisitor{

publicvoidaskWorkExperience(Stringname);

publicvoidaskScienceAchievement(Stringname);

@Override

System.out.printf("请问%s:在学校取得的最大成就是什么? ",name);

}

publicvoidaskWorkExperience(Stringname){

但在实际的应用中,我发现有些例子并不是如此。有些例子中并没有稳定的数据结构,而是稳定的算法。在树义看来,访问者模式是:把不变的固定起来,变化的开放出去。

System.out.printf("请问%s:工作上最难忘的事情是什么? ",name);

}

有这些多优点,但也有这么多缺点,那实际工作中我们应该怎么判断是否用访问者模式呢?总的原则就是扬长避短,即当场景完全利用了访问者模式的优点,规避了访问者模式的缺点的时候,就是使用访问者模式的最佳时机。

Visitor访问者接口。访问者接口定义了访问者可以做的事情。这个需要你去分析哪些是可变的,将这些可变的内容抽象成访问者接口的方法,开放出去。而被访问者的信息,其实就是通过访问者的参数传递过去。

publicvoidaskScienceAchievement(Stringname){

System.out.printf("请问%s:最大的科研成果是什么?",name);

}

publicvoidaskSchoolExperience(Stringname);

publicclassScientist{

需要注意的是,我们学习设计模式重点是理解类与类之间的关系,以及他们传递的信息。至于是通过什么方式传递的,是通过accept()方法,还是通过构造函数,都不是重点。

那么如果我希望统计一下所有文件及文件夹的个数呢?

privateVisitorvisitor;

privateScientist(){}

}

publicFileVisitResultvisitFile(Tfile,BasicFileAttributesattrs)

publicvoidaccept(Visitorvisitor){

this.visitor=visitor;

}

publicvoidinterview(){

一般来说,访问者模式的类结构如下图所示:

System.out.println("------------访问开始------------");

System.out.println("---开始聊学校经历---");

ClassVisitor相当于抽象访问者接口。ClassReader对象创建之后,需要调用accept()方法,传入一个ClassVisitor对象。在ClassReader的不同时期会调用ClassVisitor对象中不同的visit()方法,从而实现对字节码的修改。

visitor.askSchoolExperience(name);

System.out.println("---开始聊工作经历---");

publicinterfaceFileVisitor {

visitor.askWorkExperience(name);

publicvoidaskSchoolExperience(Stringname){

System.out.println("---开始聊科研成果---");

}

}

最后我们声明一个场景类Client,来模拟访谈这一过程。

publicclassClient{

publicstaticvoidmain(String[]args){

Scientistyang=newScientist("杨振宁");

yang.accept(newXinhuaVisitor());

yang.interview();

}

}

------------访问开始------------

---开始聊学校经历---

请问杨振宁:在学校取得的最大成就是什么?

请问杨振宁:工作上最难忘的意见事情是什么?

---开始聊科研成果---

请问杨振宁:最大的科研成果是什么?

看到这里,大家对于访问者模式的本质有了更感性的认识(把不变的固定起来,变化的开放出去)。在这个例子中,不变的固定的就是访谈流程,变化的就是你可以提不同的问题。

接着声明一个XinhuaVisitor类去实现Visitor类,这表示是新华社的一个记者(访问者)想去访问科学家。

我们举生活中一个例子来聊聊:某科学家接受记者访谈。我们都知道科学家接受访问,肯定是有流程上的限制的,不可能让你随便问。我们假设这个过程是:先问科学家的学校经历,再聊你的工作经历,最后聊你的科研成果。那么在这个过程中,固定的是什么东西呢?固定的是接受采访的流程。变化的是什么呢?变化的是不同的记者,针对学校经历,可能会提不同的问题。

ConcreteVisitor具体访问者。具体访问者定义了具体某一类访问者的实现。对于新华社记者来说,他们更关心杨振宁科学成果方面的事情,于是他们提问的时候更倾向于挖掘成果。但对于青年报记者来说,他们的读者是青少年,他们更关心杨振宁在学习、工作中的那种精神。

对于访问者模式来说,最重要的莫过于Visitor、ConcreteVisitor、Element这三个类了。Visitor、ConcreteVisitor定义访问者具体能做的事情,被访问者的参数通过参数传递给访问者。Element则通过各种方法拿到被访问者对象,常用的是通过accept()方法,但这并不是绝对的。

根据我们之前的理解,访问者模式其实就是要把不变的东西固定起来,变化的开放出去。那么对于科学家接受访谈这个事情,我们可以这么将其抽象化。

@Override

01什么是访问者模式?

@Override

02访问者模式的实际应用

图片

前面我们用一个生活的例子帮助大家理解访问者模式,相信大家对访问者模式应该有了个感性的理解了。为了回归编程实践本身,让大家对访问者模式能有更好的实践理解。下面我们将从软件编程上讲讲访问者模式在开源框架中的应用。

很简单的做法,其实就是直接做一个树的遍历,然后将名字打印出来呀!

JDK中有文件操作,我们自然是清楚的。有文件操作,那自然就会有文件夹的遍历操作,即访问某个文件夹下面的所有文件或文件夹。试想一下,如果我们想要打印出某个文件夹下所有文件及文件夹的名字,我们需要怎么做?

没错,这确实是正确答案!

ASM是Java的字节码增强技术,这里面就用到了访问者模式,主要是用来进行字节码的修改。在ASM中与此相关的三个类分别是:ClassReader、ClassVisitor、ClassWriter。

访问者模式的优点很明显,即隔离了变化的东西,固定了不变的东西,使得整体的可维护性更强、具有更强的扩展性。但它也带来了设计模式通用的一些缺点,例如:

那就再遍历一次,然后用一个计数器去一直加一呗!

publicScientist(Stringname){

但你是否发现了这两个过程中,我们有一个相同的操作:遍历文件树。无论是打印文件名,还是计算文件树,我们都需要去遍历文件树。而无论哪一个过程,我们最终要的其实就是访问文件。

还记得我们说过设计模式的本质是什么吗?设计模式的本质是找出不变的东西,再找出变化的东西,然后找到合适的数据结构(设计模式)去承载这种变化。

在这个例子里,不变的东西是文件树的遍历,变化的是对于文件的不同访问操作。很显然,访问者模式是比较适合承载这种变化的。我们可以把这种不变的东西(文件树的遍历)固定起来,把变化的东西(文件的具体操作)开放出去。JDK对于文件树的遍历,其实就是使用访问者模式实现的。

JDK中声明了一个FileVisitor接口,定义了遍历者可以做的操作。

FileVisitResultpreVisitDirectory(Tdir,BasicFileAttributesattrs);

FileVisitResultvisitFile(Tfile,BasicFileAttributesattrs)

}

throwsIOException;

FileVisitResultvisitFileFailed(Tfile,IOExceptionexc)

FileVisitResultpostVisitDirectory(Tdir,IOExceptionexc)

throwsIOException;

publicclassXinhuaVisitorimplementsVisitor{

this.name=name;

虽然使用访问者模式会让被访问者的变更变得更加困难,但如果被访问者很稳定,基本不会变更,那这个缺点不就去除了么。例如在ASM的例子中,元素是ClassReader,其存储了字节码的结构。而字节码结构完全不会轻易改变,所以在这个「被访问者的变更变得更加困难」的缺点也就不存在了。

FileVisitor中定义的visitFile()方法,其实就是对于文件的访问。被访问者(文件)的信息通过第一个参数file传递过来。这样遍历者就可以访问文件的内容了。

SimpleFileVisitor则是对于FileVisitor接口的实现,该类中仅仅是做了简单的参数校验,并没有太过的逻辑。

publicclassSimpleFileVisitor implementsFileVisitor {

@Override

publicFileVisitResultpreVisitDirectory(Tdir,BasicFileAttributesattrs)

}

throwsIOException

{

Objects.requireNonNull(dir);

接着声明一个Scientist类,表明是一个科学家。科学家通过一个accept()方法接收记者(访问者)的访问申请,将其存储起来。科学家定义了一个interview方法,将访问的流程固定死了,只有教你问什么的时候,我才会让你(记者)提问。

Objects.requireNonNull(attrs);

}

}

@Override

{

03我们该如何使用?

Objects.requireNonNull(attrs);

returnFileVisitResult.CONTINUE;

visitor.askScienceAchievement(name);

throwsIOException;

//....其他省略

}

privateStringname;

FileVisitor类和SimpleFileVisitor类对应的就是UML类图中的Visitor和ConcreteVisitor类。而Element元素,对应的其实是JDK中的Files类。

运行的结果为:

图片

Files文件中遍历文件树是通过walkFileTree()方法实现的。在walkFileTree()方法中实现了树的遍历,在遍历到文件的时候会通过visitor类的visitFile方法调用遍历者的方法,将遍历到的文件传递给遍历者,从而达到分离变化的目的。

ASM修改字节码

throwsIOException

文件树遍历

ClassWriter是ClassVisitor的是实现类,它负责将修改后的字节码输出为字节数组。

ClassReader类相当于访问者模式中的Element元素。它将字节数组或class文件读入内存中,并以树的数据结构表示。该类定义了一个accept方法用来和visitor交互。

图片

图片

图片

对于ASM这种场景而言,字节码规范是非常严格且稳定的,如果随便更改可能出问题。但我们又需要对字节码进行动态修改,从而达到某些目的。在这种情况下,ASM的设计者采用了访问者模式将变化的部分隔离开来,将不变的部分固定下来,从而达到了灵活扩展的目的。

从上面几个例子,我们大致可以明白访问者模式的使用场景:某些较为稳定的东西(数据结构或算法),不想直接被改变但又想扩展功能,这时候适合用访问者模式。

说到对于访问者模式使用场景的定义,我们会觉得模板方法模式与这个使用场景的定义很像。但它们还是有些许差别的。访问者模式的变化与非变化(即访问者与被访问者)之间,它们只是简单的包含关系,而模板方法模式的变化与非变化则是继承关系。但它们也确实有类似的地方,即都是封装了固定不变的东西,开放了变动的东西。

类结构变得复杂。之前我们可是简单的调用关系,现在则是多个类之间的继承和组合关系。从一定程度上,提高了对开发人员的要求,提高了研发成本。

被访问者的变更变得更加困难。例如我们上面科学家访谈的例子,如果科学家访谈希望新增一个环节,那么Scientist类需要修改,Visitor类、XinhuaVisitor类都需要修改。

}

returnFileVisitResult.CONTINUE;

而「类结构变得复杂」这个缺点,则是需要根据当时业务的复杂程度来看的。如果当时业务很简单,而且变化也不大,那么使用设计模式完全是多余的。但是如果当时业务很复杂了,我们还是在一个类里做修改,那么很大可能性会出大问题。这时候就需要用设计模式来承载复杂的业务结构了。

责任编辑:ysman
0
免责声明:
① 凡本网注明稿件来源:“智品网”的所有文字、图片和音视频稿件,版权均属智品网所有,任何媒体、网站或 个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发表。已经本网协议授权的媒体、网站,在下载使用时必须注明稿 件来源:“智品网”,违者本网将依法追究责任。
② 本网未注明稿件来源:“智品网”的文/图等稿件均为转载稿,本网转载出于传递更多信息之目的,并不意味着赞同其观点或 证实其内容的真实性。如其他媒体、网站或个人从本网下载使用,必须保留本网注明的“稿件来源”,并自负版权等法律责任。如擅 自篡改为稿件来源:“智品网”,本网将依法追究责任。如对稿件内容有疑议,请及时与我们联系。 新闻纠错:748492175 邮箱:748492175@qq.com
③ 如本网转载稿涉及版权等问题,请作者在两周内速与智品网联系。

友情链接

关于我们 联系方式 招聘信息 版权声明 网站地图