访问者模式——操作复杂对象结构

1、简介

1.1、概述

访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分。这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。

在使用访问者模式时,被访问的元素通常不是单独存在的,它们存储在一个集合中,这个集合称为"对象结构"。访问者通过遍历对象结构实现对其中存储的元素的逐个操作。

1.2、定义

访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。

2、解析

2.1、UML类图

访问者模式的结构较为复杂,其结构如下图所示。

可以看出,在访问者模式结构图中包含以下5个角色:

  1. Visitor(抽象访问者):抽象访问者为对象结构中每个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型。具体访问者需要实现这些操作方法,提供对这些元素的访问操作。
  2. ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每个操作用于访问对象结构中一种类型的元素。
  3. Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。
  4. ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
  5. ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,例如一个List对象或一个Set对象。

访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。访问者模式包括两个层次结构:一个是访问者层次结构,提供了抽象访问者和具体访问者;另一个是元素层次结构,提供了抽象元素和具体元素。相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性。

2.2、代码示例

在访问者模式中,抽象访问者定义了访问元素对象的方法。通常为每一种类型的元素对象都提供一个访问方法,而具体访问者可以实现这些访问方法。这些访问方法的命名一般有两种方式:一种是直接在方法名中标明待访问元素对象的具体类型,例如visitElementA(ElementA elementA);另一种是统一取名为visit(),通过参数类型的不同来定义一系列重载的visit()方法。当然,如果所有的访问者对某一类型的元素的访问操作都相同,则可以将操作代码移到抽象访问者类中。其典型代码如下:

java 复制代码
/**
 * @Description: 抽象访问者
 * @Author: yangyongbing
 * @CreateTime: 2023/08/03
 * @Version: 1.0
 */
abstract class Visitor {
    public abstract void visit(ConcreteElementA elementA);
    public abstract void visit(ConcreteElementB elementA);

    public void visit(ConcreteElementC concreteElementc){
        // 元素ConcreteElementC操作代码
    }
}

在这里使用了重载visit()方法的方式来定义多个方法用于操作不同类型的元素对象。在抽象访问者Visitor类的子类ConcreteVisitor中实现了抽象的访问方法,用于定义对不同类型元素对象的操作。具体访问者类典型代码如下:

java 复制代码
/**
 * @Description:
 * @Author: yangyongbing
 * @CreateTime: 2023/08/03  21:38
 * @Version: 1.0
 */
public class ConcreteVisitor extends Visitor{
    @Override
    public void visit(ConcreteElementA elementA) {
        // 元素 ConcreteElementA操作代码
    }

    @Override
    public void visit(ConcreteElementB elementA) {
       // 元素 ConcreteElementB操作代码
    }
}

对于元素类而言,在其中一般都定义了一个accept()方法,用于接受访问者的访问。典型的抽象元素类代码如下:

java 复制代码
/**
 * @Description: 抽象元素
 * @Author: yangyongbing
 * @CreateTime: 2023/08/03  21:32
 * @Version: 1.0
 */
interface Element {
    void accept(Visitor visitor);
}

需要注意的是,该方法传入了一个抽象访问者Visitor类型的参数,即针对抽象访问者进行编程,而不是具体访问者。在程序运行时再确定具体访问者的类型,并调用具体访问者对象的visit()方法实现对元素对象的操作。在抽象元素类Element的子类中实现了accept()方法,用于接受访问者的访问。在具体元素类中还可以定义不同类型的元素所特有的业务方法,其典型代码如下:

java 复制代码
/**
 * @Description: 具体元素
 * @Author: yangyongbing
 * @CreateTime: 2023/08/03
 * @Version: 1.0
 */
public class ConcreteElementA implements Element{
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public void operationA(){
        // 业务方法
    }
}

在具体元素类ConcreteElementA的accept()方法中,通过调用Visitor类的visit()方法实现对元素的访问,并以当前对象作为visit()方法的参数。其具体执行过程如下:

(1)调用具体元素类的accept(Visitor visitor)方法,并将Visitor子类对象作为其参数。

(2)在具体元素类accept(Visitor visitor)方法内部调用传入的Visitor对象的visit()方法,例如visit(ConcreteElementA elementA)。将当前具体元素类对象(this)作为参数,例如visitor.visit(this)。

(3)执行Visitor对象的visit()方法,在其中还可以调用具体元素对象的业务方法。

这种调用机制也称为"双重分派"。正因为使用了双重分派机制,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象作为参数传入具体元素对象的accept()方法。程序运行时将回调在新增Visitor类中定义的visit()方法,从而增加新的元素访问方式。

在访问者模式中,对象结构是一个集合,它用于存储元素对象并接受访问者的访问,其典型代码如下:

java 复制代码
/**
 * @Description: 对象结构
 * @Author: yangyongbing
 * @CreateTime: 2023/08/03  21:48
 * @Version: 1.0
 */
public class ObjectStructure {
    // 定义一个集合用于存储元素对象
    private List<Element> list=new ArrayList<>();
    
    // 接受访问者的访问操作
    public void accept(Visitor visitor){
        Iterator<Element> iterator = list.iterator();
        while (iterator.hasNext()){
            // 遍历访问集合中的每一个元素
            iterator.next().accept(visitor);
        }
    }
    
    public void addElement(Element element){
        list.add(element);
    }

    public void removeElement(Element element){
        list.remove(element);
    }
}

在对象结构中可以使用迭代器对存储在集合中的元素对象进行遍历,并逐个调用每一个对象的accept()方法,实现对元素对象的访问操作。

2.3、访问者模式与组合模式联用

在访问者模式中,包含一个用于存储元素对象集合的对象结构,通常可以使用迭代器来遍历对象结构。具体元素之间如果存在整体与部分关系,有些元素作为容器对象,有些元素作为成员对象,则可以使用组合模式来组织元素。引入组合模式后的访问者模式结构图如下图所示。

需要注意的是,在上图所示结构中,由于叶子元素的遍历操作已经在容器元素中完成,因此要防止单独将已增加到容器元素中的叶子元素再次加入对象结构中。对象结构中只保存容器元素和孤立的叶子元素。

3、访问者模式总结

由于访问者模式的使用条件较为苛刻,本身结构也较为复杂,因此在实际应用中使用频率不是特别高。当系统中存在一个较为复杂的对象结构,且不同访问者对其所采取的操作也不相同时,可以考虑使用访问者模式进行设计。在XML文档解析、编译器的设计、复杂集合对象的处理等领域,访问者模式得到了一定的应用。

3.1、主要优点

  1. 增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合开闭原则。
  2. 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
  3. 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

3.2、主要缺点

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了开闭原则的要求。
  2. 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

3.3、适用场景

  1. 一个对象结构包含多种类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
  2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而且需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。访问者模式将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
  3. 对象结构中元素对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
相关推荐
WangMing_X6 天前
C# 23种设计模式(4)访问者模式(Visitor Pattern)
开发语言·设计模式·c#·访问者模式
我码玄黄9 天前
JS设计模式之访问者模式
javascript·设计模式·访问者模式
博风10 天前
设计模式:24、访问者模式
设计模式·访问者模式
喵手17 天前
设计模式探秘:迭代器模式与访问者模式详解
设计模式·迭代器模式·访问者模式
橘色的喵18 天前
C++编程:模拟实现CyberRT的DataVisitor和DataDispatcher
c++·访问者模式·观察者·cyberrt·datavisitor·datadispatcher
小白不太白9501 个月前
设计模式之 访问者模式
java·设计模式·访问者模式
蓝田~1 个月前
访问者模式
访问者模式
萨达大1 个月前
23种设计模式-访问者(Visitor)设计模式
java·c++·设计模式·软考·访问者模式·软件设计师·行为型设计模式
丶白泽2 个月前
重修设计模式-行为型-访问者模式
java·设计模式·访问者模式·1024程序员节
努力找工作的OMArmy2 个月前
软件开发----设计模式每日刷题(转载于牛客)
java·单例模式·设计模式·策略模式·访问者模式·模板方法模式·开闭原则