0

0

JS如何实现访问者模式?访问者的结构

畫卷琴夢

畫卷琴夢

发布时间:2025-08-24 08:00:03

|

488人浏览过

|

来源于php中文网

原创

访问者模式的核心思想是将操作算法与对象结构分离,通过定义accept方法和访问者类实现解耦,解决了操作与结构紧耦合、难以扩展新操作及逻辑分散的痛点。

js如何实现访问者模式?访问者的结构

JavaScript中实现访问者模式,其核心在于将对对象结构的操作(算法)从对象结构本身中分离出来。访问者的结构通常包含两个主要部分:可接受访问者(Acceptor)的对象元素,以及实际执行操作的访问者(Visitor)对象。当一个元素需要被访问时,它会“接受”一个访问者,并调用访问者对象上对应其类型的方法,从而将自身传递给访问者进行处理。

解决方案

要实现访问者模式,我们通常会定义一组“元素”类和一组“访问者”类。

首先,是可被访问的元素(Element)部分。每个具体的元素类都需要有一个

accept
方法,这个方法接收一个访问者实例作为参数。
accept
方法的职责很简单:它会调用传入的访问者实例上对应自身类型的方法,并将自己作为参数传过去。

// 抽象元素概念(在JS中通常通过约定实现)
// class Element {
//     accept(visitor) {
//         throw new Error("This method must be overridden!");
//     }
// }

// 具体元素A
class ConcreteElementA {
    constructor(data) {
        this.data = data;
    }

    accept(visitor) {
        // 调用访问者中针对ConcreteElementA的方法
        visitor.visitConcreteElementA(this);
    }

    getSpecificDataA() {
        return `A: ${this.data}`;
    }
}

// 具体元素B
class ConcreteElementB {
    constructor(value) {
        this.value = value;
    }

    accept(visitor) {
        // 调用访问者中针对ConcreteElementB的方法
        visitor.visitConcreteElementB(this);
    }

    getSpecificValueB() {
        return `B: ${this.value}`;
    }
}

接着,是访问者(Visitor)部分。访问者是一个接口(在JS中同样是概念上的约定),它定义了针对每种具体元素类型的方法。每个具体的访问者类都会实现这些方法,并在其中封装针对对应元素类型的操作逻辑。

// 抽象访问者概念(同样是约定)
// class Visitor {
//     visitConcreteElementA(elementA) {
//         throw new Error("This method must be overridden!");
//     }
//     visitConcreteElementB(elementB) {
//         throw new Error("This method must be overridden!");
//     }
// }

// 具体访问者1:执行打印操作
class PrintVisitor {
    visitConcreteElementA(elementA) {
        console.log(`打印访问者处理元素A: ${elementA.getSpecificDataA()}`);
    }

    visitConcreteElementB(elementB) {
        console.log(`打印访问者处理元素B: ${elementB.getSpecificValueB()}`);
    }
}

// 具体访问者2:执行计算操作
class CalculateVisitor {
    constructor() {
        this.totalSum = 0;
    }

    visitConcreteElementA(elementA) {
        // 假设elementA.data是数字
        this.totalSum += Number(elementA.data);
        console.log(`计算访问者处理元素A,当前总和: ${this.totalSum}`);
    }

    visitConcreteElementB(elementB) {
        // 假设elementB.value是数字
        this.totalSum += Number(elementB.value);
        console.log(`计算访问者处理元素B,当前总和: ${this.totalSum}`);
    }

    getTotalSum() {
        return this.totalSum;
    }
}

最后,是客户端代码。客户端会创建一系列元素对象,然后创建具体的访问者对象,并将这些访问者“派发”给每个元素。

const elements = [
    new ConcreteElementA(10),
    new ConcreteElementB(20),
    new ConcreteElementA(30),
    new ConcreteElementB(40)
];

console.log("--- 使用打印访问者 ---");
const printVisitor = new PrintVisitor();
elements.forEach(element => {
    element.accept(printVisitor); // 元素接受访问者
});

console.log("\n--- 使用计算访问者 ---");
const calculateVisitor = new CalculateVisitor();
elements.forEach(element => {
    element.accept(calculateVisitor);
});
console.log(`\n最终计算总和: ${calculateVisitor.getTotalSum()}`);

通过这种方式,我们可以在不修改现有元素类的情况下,为它们添加新的操作(通过创建新的访问者类)。

访问者模式的核心思想是什么?它解决了哪些痛点?

访问者模式的核心思想,说白了,就是将操作(算法)与它所作用的对象结构分离开来。想想看,我们有很多不同类型的对象,比如一个文档里的段落、图片、表格等等。我们可能需要对这些对象执行各种操作:打印、导出为PDF、计算字数、检查拼写。如果把所有这些操作都写在每个对象自己的类里,那这些类就会变得非常臃肿,而且每增加一个新操作,你都得去修改所有相关的对象类。这显然违反了“开闭原则”——对扩展开放,对修改关闭。

这就是访问者模式试图解决的痛点:

  1. 操作与结构耦合过紧: 传统做法是把操作逻辑直接写在对象的方法里。但如果这些操作是多变的,或者涉及多种对象类型,那么这些对象类就会变得非常复杂,职责不单一。
  2. 难以添加新操作: 每当需要对一组对象添加一个全新的操作时,你就得遍历所有相关的对象类,并修改它们。这不仅耗时,还容易引入错误,尤其是在大型系统中。
  3. 操作逻辑分散: 某个特定的操作逻辑,比如“导出为PDF”,可能需要处理所有类型的文档元素。如果这些逻辑分散在每个元素类中,维护和理解起来就会很困难。而访问者模式将特定操作的所有逻辑集中在一个访问者类中,提高了内聚性。

通过引入一个独立的访问者对象,我们把“做什么”从“谁来做”中剥离出来。元素对象只知道自己可以被访问,并把“访问”这个动作委托给访问者。而具体的访问者则知道如何针对每种类型的元素执行特定的操作。这种解耦让系统在需要添加新操作时变得非常灵活,你只需要新建一个访问者类,而无需改动现有的元素结构。

在JavaScript中实现访问者模式有哪些常见的变体或考量?

JavaScript在实现访问者模式时,因为其动态类型特性和缺乏传统接口的约束,会带来一些独特的考量和实现上的灵活性。

一个最明显的不同是多态性实现。在Java或C#这类静态语言中,访问者模式通常利用方法重载(overloading)来区分不同的元素类型,即

visit(ConcreteElementA elementA)
visit(ConcreteElementB elementB)
。但在JavaScript中,并没有传统意义上的方法重载。我们通常通过在访问者对象上定义不同名称的方法来模拟这种行为,比如
visitConcreteElementA
visitConcreteElementB
,然后由元素在
accept
方法中动态调用。

// 元素A的accept方法
accept(visitor) {
    visitor.visitConcreteElementA(this); // 明确调用对应名称的方法
}

这要求元素知道访问者上对应自己类型的方法名,这在一定程度上增加了耦合,但也非常直观。

另一个常见考量是访问者状态管理。访问者模式的强大之处在于,一个访问者实例可以在遍历对象结构的过程中积累状态。例如,一个

CalculateVisitor
可以维护一个
totalSum
。这在处理像树形结构(如DOM树或AST)时特别有用,访问者可以自上而下地收集信息,或自下而上地聚合结果。

我要服装批发网
我要服装批发网

由逍遥网店系统修改而成,修改内容如下:前台商品可以看大图功能后台商品在线添加编辑功能 (允许UBB)破解了访问统计系统增加整合了更加强大的第三方统计系统 (IT学习者v1.6)并且更新了10月份的IP数据库。修正了后台会员订单折扣金额处理错误BUG去掉了会员折扣价这个功能,使用市场价,批发价。这样符合实际的模式,批发价非会员不可看修正了在线编辑无法使用 “代码&rdqu

下载
class CalculateVisitor {
    constructor() {
        this.totalSum = 0; // 访问者可以维护自己的状态
    }
    // ... visit methods ...
}

此外,遍历逻辑也是一个关键点。访问者模式本身并不规定如何遍历对象结构。它只是提供了一种机制,让一个操作能够作用于结构中的每个元素。对于复合对象(比如一个包含子元素的父元素),其

accept
方法通常会先让访问者访问自身,然后递归地让其子元素也接受同一个访问者。

// 假设有一个CompositeElement
class CompositeElement {
    constructor(name) {
        this.name = name;
        this.children = [];
    }

    add(element) {
        this.children.push(element);
    }

    accept(visitor) {
        visitor.visitCompositeElement(this); // 访问自身
        this.children.forEach(child => {
            child.accept(visitor); // 递归访问子元素
        });
    }
}

这种递归的

accept
调用是实现对复杂结构(如抽象语法树或文件系统)进行操作的关键。在JavaScript中,由于其函数式编程的灵活性,你也可以将遍历逻辑(例如深度优先或广度优先)与访问者模式结合得更紧密,或者使用外部迭代器来驱动访问过程。

什么时候不适合使用访问者模式?有没有更好的替代方案?

虽然访问者模式在处理特定问题时非常优雅,但它绝不是一个“银弹”,盲目使用反而可能引入不必要的复杂性。

首先,当你的对象结构不稳定,但操作相对固定时,访问者模式可能就不那么合适了。访问者模式的优势在于“对扩展新操作开放,对修改现有结构关闭”。但如果你的元素类(比如

ConcreteElementA
,
ConcreteElementB
)经常需要增删改,那么每次结构变化,你都得去修改所有的访问者类,因为每个访问者都必须为新的或修改过的元素类型添加或调整
visit
方法。这种情况下,维护成本会迅速上升,甚至可能不如直接在元素类中添加方法来得简单。

其次,对于非常简单、职责单一的操作,引入访问者模式可能会显得“杀鸡用牛刀”。如果某个操作只需要处理一两种元素类型,或者逻辑非常简单,直接在元素类中添加一个方法,或者使用一个简单的函数来处理,可能会更直观、代码量更少。过度设计有时比欠设计更糟糕。

那么,有没有更好的替代方案呢?当然有,这取决于具体的场景和需求:

  1. 多态性(Polymorphism): 这是最直接也最常用的替代方案。如果操作本身就是元素固有的行为,并且每个元素对其行为的实现方式不同,那么直接在每个元素类中定义相同的方法名(多态方法)是最自然的选择。例如,如果

    draw()
    操作是每个图形都应该有的,那么就让
    Circle
    Square
    都实现自己的
    draw()
    方法。这比引入一个
    DrawVisitor
    要简单得多。

    class Circle {
        draw() { console.log("绘制圆形"); }
    }
    class Square {
        draw() { console.log("绘制方形"); }
    }
    const shapes = [new Circle(), new Square()];
    shapes.forEach(shape => shape.draw()); // 简单直接
  2. 策略模式(Strategy Pattern): 如果你希望在运行时切换算法,而不是在编译时就确定,策略模式可能更合适。访问者模式侧重于在不修改对象结构的情况下添加新操作,而策略模式侧重于封装和切换不同的算法实现。在某些场景下,两者可能会有重叠,但它们的关注点不同。

  3. 函数式编程方法: 在JavaScript中,我们经常使用高阶函数和数组方法(如

    map
    ,
    filter
    ,
    reduce
    )来处理集合数据。如果你只是想对一个数组或集合中的所有对象执行某种操作,并且这些操作不需要复杂的类型判断或累积状态,那么简单地传递一个回调函数可能就足够了。

    const elements = [{ type: 'A', data: 10 }, { type: 'B', value: 20 }];
    elements.map(item => {
        if (item.type === 'A') { /* 处理A */ }
        else if (item.type === 'B') { /* 处理B */ }
        return item; // 或者返回处理后的新对象
    });

    这种方式在处理扁平结构或简单转换时非常有效,避免了类的定义和

    accept
    方法的额外开销。

总之,选择哪种模式,关键在于权衡。访问者模式在对象结构稳定、但需要频繁添加新操作的场景下表现出色。但在其他情况下,简单直接的多态、策略模式或纯粹的函数式方法可能才是更优解。没有一个模式是万能的,适合的才是最好的。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

15

2025.11.27

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1134

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

213

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1893

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

21

2026.01.19

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

75

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

36

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

61

2025.11.17

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

8

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 8万人学习

C 教程
C 教程

共75课时 | 4.3万人学习

C++教程
C++教程

共115课时 | 14.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号