《JAVA与模式》之访问者模式

Java设计模式Java设计模式之(十四)访问者模式(Visitor Pattern) 访问者模式属于行为型模式。 在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。 一、访问者模式介绍 1.1 模式意图 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 1.2 通用类图 二、模版方法模式的应用 所有设计模式的代码实现例子都 阅读详情

系列文章目录



前言

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。
在这里插入图片描述
在阎宏博士的《JAVA与模式》一书中开头是这样描述访问者(Visitor)模式的:

访问者模式是对象的行为模式。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。


一、分派的概念

变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type);而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。比如:

List list = null;
list = new ArrayList();
  声明了一个变量list,它的静态类型(也叫明显类型)是List,而它的实际类型是ArrayList。

根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。

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

动态分派(Dynamic Dispatch)发生在运行时期,动态分派动态地置换掉某个方法。

静态分派
  Java通过方法重载支持静态分派。用墨子骑马的故事作为例子,墨子可以骑白马或者黑马。墨子与白马、黑马和马的类图如下所示:
在这里插入图片描述
在这个系统中,墨子由Mozi类代表

复制代码
public class Mozi {

public void ride(Horse h){
    System.out.println("骑马");
}

public void ride(WhiteHorse wh){
    System.out.println("骑白马");
}

public void ride(BlackHorse bh){
    System.out.println("骑黑马");
}

public static void main(String[] args) {
    Horse wh = new WhiteHorse();
    Horse bh = new BlackHorse();
    Mozi mozi = new Mozi();
    mozi.ride(wh);
    mozi.ride(bh);
}

}
复制代码
  显然,Mozi类的ride()方法是由三个方法重载而成的。这三个方法分别接受马(Horse)、白马(WhiteHorse)、黑马(BlackHorse)等类型的参数。

那么在运行时,程序会打印出什么结果呢?结果是程序会打印出相同的两行“骑马”。换言之,墨子发现他所骑的都是马。

为什么呢?两次对ride()方法的调用传入的是不同的参数,也就是wh和bh。它们虽然具有不同的真实类型,但是它们的静态类型都是一样的,均是Horse类型。

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

动态分派
  Java通过方法的重写支持动态分派。用马吃草的故事作为例子,代码如下所示:

复制代码
public class Horse {

public void eat(){
    System.out.println("马吃草");
}

}
复制代码
复制代码
public class BlackHorse extends Horse {

@Override
public void eat() {
    System.out.println("黑马吃草");
}

}
复制代码
复制代码
public class Client {

public static void main(String[] args) {
    Horse h = new BlackHorse();
    h.eat();
}

}
复制代码
  变量h的静态类型是Horse,而真实类型是BlackHorse。如果上面最后一行的eat()方法调用的是BlackHorse类的eat()方法,那么上面打印的就是“黑马吃草”;相反,如果上面的eat()方法调用的是Horse类的eat()方法,那么打印的就是“马吃草”。

所以,问题的核心就是Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。这样一来,上面最后一行的eat()方法调用的是BlackHorse类的eat()方法,打印的是“黑马吃草”。

二、分派的类型

一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参数统称做方法的宗量。比如下面例子中的Test类

复制代码
public class Test {

public void print(String str){
    System.out.println(str);
}

}
复制代码
  在上面的类中,print()方法属于Test对象,所以它的接收者也就是Test对象了。print()方法有一个参数是str,它的类型是String。

根据分派可以基于多少种宗量,可以将面向对象的语言划分为单分派语言(Uni-Dispatch)和多分派语言(Multi-Dispatch)。单分派语言根据一个宗量的类型进行对方法的选择,多分派语言根据多于一个的宗量的类型对方法进行选择。

C++和Java均是单分派语言,多分派语言的例子包括CLOS和Cecil。按照这样的区分,Java就是动态的单分派语言,因为这种语言的动态分派仅仅会考虑到方法的接收者的类型,同时又是静态的多分派语言,因为这种语言对重载方法的分派会考虑到方法的接收者的类型以及方法的所有参数的类型。

在一个支持动态单分派的语言里面,有两个条件决定了一个请求会调用哪一个操作:一是请求的名字,而是接收者的真实类型。单分派限制了方法的选择过程,使得只有一个宗量可以被考虑到,这个宗量通常就是方法的接收者。在Java语言里面,如果一个操作是作用于某个类型不明的对象上面,那么对这个对象的真实类型测试仅会发生一次,这就是动态的单分派的特征。

双重分派
  一个方法根据两个宗量的类型来决定执行不同的代码,这就是“双重分派”。Java语言不支持动态的多分派,也就意味着Java不支持动态的双分派。但是通过使用设计模式,也可以在Java语言里实现动态的双重分派。

在Java中可以通过两次方法调用来达到两次分派的目的。类图如下所示:
在这里插入图片描述
在图中有两个对象,左边的叫做West,右边的叫做East。现在West对象首先调用East对象的goEast()方法,并将它自己传入。在East对象被调用时,立即根据传入的参数知道了调用者是谁,于是反过来调用“调用者”对象的goWest()方法。通过两次调用将程序控制权轮番交给两个对象,其时序图如下所示:
在这里插入图片描述
这样就出现了两次方法调用,程序控制权被两个对象像传球一样,首先由West对象传给了East对象,然后又被返传给了West对象。

但是仅仅返传了一下球,并不能解决双重分派的问题。关键是怎样利用这两次调用,以及Java语言的动态单分派功能,使得在这种传球的过程中,能够触发两次单分派。

动态单分派在Java语言中是在子类重写父类的方法时发生的。换言之,West和East都必须分别置身于自己的类型等级结构中,如下图所示:
在这里插入图片描述
源代码
  West类

复制代码
public abstract class West {

public abstract void goWest1(SubEast1 east);

public abstract void goWest2(SubEast2 east);

}
复制代码
  SubWest1类

复制代码
public class SubWest1 extends West{

@Override
public void goWest1(SubEast1 east) {
    
    System.out.println("SubWest1 + " + east.myName1());
}

@Override
public void goWest2(SubEast2 east) {
    
    System.out.println("SubWest1 + " + east.myName2());
}

}
复制代码
  SubWest2类

复制代码
public class SubWest2 extends West{
@Override
public void goWest1(SubEast1 east) {

    System.out.println("SubWest2 + " + east.myName1());
}

@Override
public void goWest2(SubEast2 east) {
    
    System.out.println("SubWest2 + " + east.myName2());
}

}
复制代码
  East类

public abstract class East {

public abstract void goEast(West west);

}
  SubEast1类

复制代码
public class SubEast1 extends East{
@Override
public void goEast(West west) {
west.goWest1(this);
}

public String myName1(){
    return "SubEast1";
}

}
复制代码
  SubEast2类

复制代码
public class SubEast2 extends East{
@Override
public void goEast(West west) {
west.goWest2(this);
}

public String myName2(){
    return "SubEast2";
}

}
复制代码
  客户端类

复制代码
public class Client {

public static void main(String[] args) {
    //组合1
    East east = new SubEast1();
    West west = new SubWest1();
    east.goEast(west);
    //组合2
    east = new SubEast1();
    west = new SubWest2();
    east.goEast(west);
}

}
复制代码
  运行结果如下

SubWest1 + SubEast1
SubWest2 + SubEast1

系统运行时,会首先创建SubWest1和SubEast1对象,然后客户端调用SubEast1的goEast()方法,并将SubWest1对象传入。由于SubEast1对象重写了其超类East的goEast()方法,因此,这个时候就发生了一次动态的单分派。当SubEast1对象接到调用时,会从参数中得到SubWest1对象,所以它就立即调用这个对象的goWest1()方法,并将自己传入。由于SubEast1对象有权选择调用哪一个对象,因此,在此时又进行一次动态的方法分派。

这个时候SubWest1对象就得到了SubEast1对象。通过调用这个对象myName1()方法,就可以打印出自己的名字和SubEast对象的名字,其时序图如下所示:
  在这里插入图片描述
由于这两个名字一个来自East等级结构,另一个来自West等级结构中,因此,它们的组合式是动态决定的。这就是动态双重分派的实现机制。

三、访问者模式的结构

访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。访问者模式的简略图如下所示:
在这里插入图片描述 数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。访问者模式的示意性类图如下所示:
在这里插入图片描述
访问者模式涉及到的角色如下:

●  抽象访问者(Visitor)角色:声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口。

●  具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。

●  抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参数。

●  具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。

●  结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如List或Set。

源代码
  可以看到,抽象访问者角色为每一个具体节点都准备了一个访问操作。由于有两个节点,因此,对应就有两个访问操作。

复制代码
public interface Visitor {
/**
* 对应于NodeA的访问操作
/
public void visit(NodeA node);
/
*
* 对应于NodeB的访问操作
*/
public void visit(NodeB node);
}
复制代码
  具体访问者VisitorA类

复制代码
public class VisitorA implements Visitor {
/**
* 对应于NodeA的访问操作
/
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/
*
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}

}
复制代码
  具体访问者VisitorB类

复制代码
public class VisitorB implements Visitor {
/**
* 对应于NodeA的访问操作
/
@Override
public void visit(NodeA node) {
System.out.println(node.operationA());
}
/
*
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println(node.operationB());
}

}
复制代码
  抽象节点类

复制代码
public abstract class Node {
/**
* 接受操作
*/
public abstract void accept(Visitor visitor);
}
复制代码
  具体节点类NodeA

复制代码
public class NodeA extends Node{
/**
* 接受操作
/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/
*
* NodeA特有的方法
*/
public String operationA(){
return “NodeA”;
}

}
复制代码
  具体节点类NodeB

复制代码
public class NodeB extends Node{
/**
* 接受方法
/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/
*
* NodeB特有的方法
*/
public String operationB(){
return “NodeB”;
}
}
复制代码
  结构对象角色类,这个结构对象角色持有一个聚集,并向外界提供add()方法作为对聚集的管理操作。通过调用这个方法,可以动态地增加一个新的节点。

复制代码
public class ObjectStructure {

private List<Node> nodes = new ArrayList<Node>();

/**
 * 执行方法操作
 */
public void action(Visitor visitor){
    
    for(Node node : nodes)
    {
        node.accept(visitor);
    }
    
}
/**
 * 添加一个新元素
 */
public void add(Node node){
    nodes.add(node);
}

}
复制代码
  客户端类

复制代码
public class Client {

public static void main(String[] args) {
    //创建一个结构对象
    ObjectStructure os = new ObjectStructure();
    //给结构增加一个节点
    os.add(new NodeA());
    //给结构增加一个节点
    os.add(new NodeB());
    //创建一个访问者
    Visitor visitor = new VisitorA();
    os.action(visitor);
}

}
复制代码
  虽然在这个示意性的实现里并没有出现一个复杂的具有多个树枝节点的对象树结构,但是,在实际系统中访问者模式通常是用来处理复杂的对象树结构的,而且访问者模式可以用来处理跨越多个等级结构的树结构问题。这正是访问者模式的功能强大之处。

准备过程时序图
  首先,这个示意性的客户端创建了一个结构对象,然后将一个新的NodeA对象和一个新的NodeB对象传入。

其次,客户端创建了一个VisitorA对象,并将此对象传给结构对象。

然后,客户端调用结构对象聚集管理方法,将NodeA和NodeB节点加入到结构对象中去。

最后,客户端调用结构对象的行动方法action(),启动访问过程。
在这里插入图片描述  访问过程时序图
在这里插入图片描述
 结构对象会遍历它自己所保存的聚集中的所有节点,在本系统中就是节点NodeA和NodeB。首先NodeA会被访问到,这个访问是由以下的操作组成的:

(1)NodeA对象的接受方法accept()被调用,并将VisitorA对象本身传入;

(2)NodeA对象反过来调用VisitorA对象的访问方法,并将NodeA对象本身传入;

(3)VisitorA对象调用NodeA对象的特有方法operationA()。

从而就完成了双重分派过程,接着,NodeB会被访问,这个访问的过程和NodeA被访问的过程是一样的,这里不再叙述。

四、访问者模式的优点

●  好的扩展性

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

●  好的复用性

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

●  分离无关行为

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

五、访问者模式的缺点

●  对象结构变化很困难

不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。

●  破坏封装

访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。

深入理解Java编程设计模式:《Java模式》全解析 设计模式是软件开发过程中为解决特定问题而形成的模式化解决方案。它们可以提高代码的复用性,降低系统各部分间的耦合度,并使得代码易于理解和维护。常见的设计模式可以分为三大类:创建型模式:涉及对象的创建过程,如单例模式、工厂模式等。结构型模式:关注如何组合类和对象以获得更大的结构,如适配器模式、装饰器模式等。行为型模式:涉及对象之间的职责分配,如观察者模式、命令模式等。 阅读详情

相关推荐

Java模式阎宏)高清晰完整PDF版

不可多得的好书,很详细的介绍设计原理和主流设计模式,包括简单工厂模式,工厂模式等等,想学好设计模式的同学可以看一下

20、解释器模式

这个模式真是不太好理解,比较复杂,我在看来这篇文章后才有了简单的了解,https://www.cnblogs.com/java-my-life/archive/2012/06/19/2552617.html,就按照这位大佬的思路,自己也记录一下吧。 1、简介 解释器模式是类的行为模式。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。 解释器模式的结构: 模式所涉及的角色如下所示: 抽象表达式(Expression)角色:声明

qq_44985319的博客 254

java模式(清晰书签版).pdf

java模式(清晰书签版).pdf java模式(清晰书签版).pdf java模式(清晰书签版).pdf java模式(清晰书签版).pdf

JAVA模式》之桥梁模式

阎宏博士的《JAVA模式》一书中开头是这样描述桥梁(Bridge)模式的:   桥梁模式是对象的结构模式。又称为柄体(Handle and Body)模式或接口(Interface)模式。桥梁模式的用意是“将抽象化(Abstraction)实现化(Implementation)脱耦,使得二者可以独立地变化”。 桥梁模式的用意   桥梁模式虽然不是一个使用频率很高的模式,但是熟悉这个...

weixin_30824277的博客 298

Java设计模式 —— 【行为型模式访问者模式(Visitor Pattern)详解

访问者模式是一种行为设计模式,它允许在不修改对象结构的情况下定义新的操作。该模式将算法对象结构分离,通过访问者角色封装对不同元素的操作。主要优点包括扩展性好、复用性高和分离无关行为,但也存在对象结构变化困难、违反依赖倒置原则等缺点。适用场景包括对象结构稳定但操作频繁变化的情况。文章还介绍了双分派技术,包括静态分派(方法重载)和动态分派(方法重写),帮助理解访问者模式的工作原理。

知唔猪的博客 1113

JAVA设计模式访问者模式【Visitor Pattern】

一、概述     访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为“对象结构”,访问者通过遍历对象结构实现对其中存储的元素的逐个操作。访问者模式是一种对象行为型模式。 二、适用场景

小吕丶Code阶区 6080

26-Java访问者模式 ( Visitor Pattern )

1. 意图主要将数据结构数据操作分离2. 主要解决稳定的数据结构和易变的操作耦合问题。3. 何时使用需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中4. 如何解决在被访问的类里面加一个对外提供接待访问者的接口5. 关键代码在数据基础类里面有一个方法接受访问者,将自身引用传入访问者6. 应用实例您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式7. 优点。

深入Java世界:探索编程之美与技术深度 1450

Java访问者模式(Visitor Pattern)

一、完成测评系统需求 将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价 有不同的种类,比如 成功、失败 等)。 传统方案 传统方式的问题分析 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则, 不利于维护。 扩展性不好,比如 增加了 新的人员类型,或者管理方法,都不好做。 引出我们会使用新的设计模式访问者模式。 二、访问者模式基本介绍 访问者模式(Visitor Pattern),封装一些作用于某

weixin_44256581的博客 811

Java模式(闫宏)

Java模式是台湾著名大师闫宏的经典著作,里面引用了很多道德经。讲述了23中设计模式,值得大家下载阅读。

Java模式阎宏经典版)

Java模式阎宏经典版,java编程学习经典。

Java模式.pdf

非常不错的书,强烈推荐! 没积分可以百度云下载,下载地址如下:https://blog.csdn.net/smileyan9/article/details/81486926

Java模式(高清版)

java模式 高清版 作者:阎宏 版本:高清版 格式:pdf

java模式

java模式--阎宏 ,清晰书签版。

Java设计模式——访问者模式【Visitor Pattern】

Java设计模式访问者模式、Visitor Pattern

scorpio的博客 383

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

访问者模式是一种行为型设计模式,用于在不改变被访问元素的类的前提下定义对这些元素的新操作。访问者模式的核心思想是将数据结构作用于结构上的操作解耦,使得操作集合可以灵活变化而不影响元素的类。访问者模式通常包含两个主要角色:访问者(Visitor)和具体元素(Concrete Element)。定义了对具体元素进行访问的接口,每个具体访问者都实现了对应的访问操作定义了接受访问者的方法,并通过该方法调用访问者的访问操作访问者模式是一种用于在不改变被访问元素的类的前提下定义对这些元素的新操作的设计模式

好久不见的流星 1687

JAVA访问者模式(Visitor Pattern)的技术指南

本文介绍了访问者模式(Visitor Pattern)这一行为型设计模式访问者模式的核心是将数据结构和操作分离,通过访问者对象定义操作,而不需修改被操作类本身。文章详细阐述了访问者模式的角色组成(访问者接口、具体访问者、元素接口、具体元素和对象结构),并提供了一个公司人员薪资调整的实际案例代码实现。该模式具有开闭原则、操作聚合和灵活性高等优点,但也存在对象结构依赖、违反单一职责原则等缺点。访问者模式适用于编译器、报表生成等需要对复杂对象结构进行多种操作的场景。

木头 1228

Java设计模式 二十三 访问者模式 (Visitor Pattern)

访问者模式通过将操作封装到访问者对象中,实现了操作对象结构的解耦。它使得可以在不修改对象结构的情况下,为对象添加新的操作,符合开放-封闭原则。不过,访问者模式的使用也带来了类数目的增加,并且在对象结构频繁变化时可能需要频繁修改访问者类,因此需要根据实际需求来权衡是否使用该模式

767

JAVA访问者模式

阎宏博士的《JAVA模式》一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。 分派的概念 变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type);而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。比如: List list = null; list =

weixin_53341657的博客 1119
上一篇: 《JAVA与模式》之解释器模式
下一篇: 《JAVA与模式》之状态模式
Java毕设王
博客等级 码龄7年 9146粉丝 942原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java毕设王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值