访问者设计模式在Js中的使用

访问者设计模式 (The Visitor design pattern)

定义和特点

访问者模式是一种行为型设计模式,用于将算法与对象结构分离。 该模式允许你定义新的操作 (访问者)而无需修改现有对象结构 (被访问者)。 通过这种方式,你可以在不改变对象结构的情况下添加新的操作

参与者

在访问者模式中,有两个主要角色:被访问者和访问者。

  • 被访问者:一个具有一组**元素和方法(method)**的对象结构。
  • 访问者:一个能够对这些元素执行不同**操作(visit)**的对象。
  • 被访问者提供了**接受(accept)**访问者的方法,以便访问者能够在需要时访问元素。

使用流程

  1. 首定义访问者接口,其中包含一组访问方法,每个方法对应一种操作。
  2. 在被访问者接口 中添加一个接受访问者的方法accept,以便访问者可以访问被访问者的元素。
  3. 被访问者的具体实现类需要实现这个接收访问者的方法,并**将自身作为参数(this)**传递给访问者的具体访问方法。
  4. 创建一个具体的访问者对象v ,并将其传递给被访问者的接受方法,即accept(v)
  5. 被访问者将根据传递的访问者对象调用相应的访问方法,从而执行特定的操作。 通过这种【改变新建不同结构的访问者,而不修改被访问者】的方式,你可以在不改变被访问者的结构的情况下,为其添加新的操作。

通俗的理解

访问者设计模式有两个部分完成,在实现不同目标的过程中保持其中一方(被访问者)的结构不变,只修改另外一方的结构(访问者)。 也就是牺牲一个保全另外一个。

作用

访问者模式允许你将算法与对象结构分离,并通过定义访问者接口和被访问者接口来实现多态性。 这种模式适用于需要对一个对象结构中的元素进行不同操作的场景,同时又希望保持对象结构的稳定性。

举例

typescript 复制代码
// 被访问者接口:点心店
interface Bakery {
  accept(visitor: CustomerVisitor): void;
  make(): string;
  makePlus(): string;
}

// 具体的被访问者:圆点心店
class CirclePastry implements Bakery {
  // 被访问者的接受方法
  accept(visitor: CustomerVisitor): void {
    // 将自身作为参数(this)传递给访问者的具体访问方法
    visitor?.visitCirclePastry(this);
  }

  make(): string {
    return "制作圆点心";
  }

  makePlus(): string {
    return "制作精品圆点心";
  }
}

// 具体的被访问者:方点心店
class SquarePastry implements Bakery {
  accept(visitor: CustomerVisitor): void {
    visitor?.visitSquarePastry(this);
  }

  make(): string {
    return "制作方点心";
  }

  makePlus(): string {
    return "制作精品方点心";
  }
}

// 访问者接口:顾客
interface CustomerVisitor {
  visitCirclePastry(pastry: CirclePastry): void;
  visitSquarePastry(pastry: SquarePastry): void;
}

// 具体的访问者:点心爱好者
class PastryLover implements CustomerVisitor {
  visitCirclePastry(pastry: CirclePastry): void {
    console.log(`点心爱好者选择了${pastry.make()}`);
  }

  visitSquarePastry(pastry: SquarePastry): void {
    console.log(`点心爱好者选择了${pastry.make()}`);
  }
}

// 使用示例
const circlePastry: Bakery = new CirclePastry();
const squarePastry: Bakery = new SquarePastry();

const pastryLover: CustomerVisitor = new PastryLover();

circlePastry.accept(pastryLover); // 输出:点心爱好者选择了制作圆点心
squarePastry.accept(pastryLover); // 输出:点心爱好者选择了制作方点心

// 在保证被访问者结构的不变的前提下通过修改访问者的结构达到完成不同操作的目的
// 访问者接口:高级顾客
interface CustomerVisitorPlus {
  visitCirclePastry(pastry: CirclePastry): void;
  visitSquarePastry(pastry: SquarePastry): void;
}

// 具体的访问者:高级点心爱好者
class PastryLoverWithMoney implements CustomerVisitorPlus {
  visitCirclePastry(pastry: CirclePastry): void {
    console.log(`点心爱好者选择了${pastry.makePlus()}`);
  }

  visitSquarePastry(pastry: SquarePastry): void {
    console.log(`点心爱好者选择了${pastry.makePlus()}`);
  }
}

const pastryLoverWithMoney: PastryLoverWithMoney = new PastryLoverWithMoney();

circlePastry.accept(pastryLoverWithMoney); // 输出:点心爱好者选择了制作高级圆点心
squarePastry.accept(pastryLoverWithMoney); // 输出:点心爱好者选择了制作高级方点心
// 可以看出来circlePastry和squarePastry都被复用了

Babel插件中的使用

在 Babel 插件中修改 AST(抽象语法树)时,通常会使用访问者模式。

  1. 定义访问者:定义一个访问者对象,该对象包含用于处理不同类型的 AST 节点的方法。每个方法对应一种 AST 节点类型,该方法将被调用以访问和处理相应类型的节点。
  2. 遍历和修改 AST:通过使用 Babel 提供的遍历器(@babel/traverse),可以遍历整个 AST。在遍历过程中,对于每个访问到的节点,将根据节点的类型调用相应的访问者方法。
  3. 修改 AST:在访问者方法中,您可以对 AST 进行修改。这可以涉及更改节点属性、替换节点、添加新节点等操作。通过修改 AST,插件可以实现源代码的转换和重写。

应用场景

  1. DOM 操作:在浏览器中,DOM(文档对象模型)表示网页的结构和内容。使用访问者模式,您可以定义一个访问者对象,该对象可以遍历 DOM 树的节点,并执行相应的操作。例如,可以创建一个访问者来查找特定类型的节点、修改节点属性或样式,或执行其他与 DOM 相关的操作。
  2. 数据结构操作:JavaScript 中有许多内置的数据结构,如数组、集合、映射等。通过使用访问者模式,您可以定义一个访问者对象,来对这些数据结构进行遍历和操作。例如,可以创建一个访问者来计算数组中的总和、过滤符合特定条件的元素,或者将映射转换为另一种形式。
  3. 编译器和解析器:在编译器和解析器中,访问者模式经常被用来处理抽象语法树(AST)。通过定义访问者对象,可以遍历 AST 并执行各种语义分析、优化或代码生成操作。这样可以将复杂的编译器逻辑分离到不同的访问者方法中,使其更易于维护和扩展。
  4. 事件处理:在浏览器中,事件处理是非常常见的任务。访问者模式可以用于处理不同类型的事件,并执行相应的操作。例如,可以创建一个访问者来处理鼠标事件、键盘事件或其他用户交互事件。
  5. 数据校验和验证:当需要对数据进行复杂的校验和验证时,访问者模式可以提供一种结构化的方法。您可以定义一个访问者对象,该对象遍历数据结构并执行各种校验逻辑。这样可以将校验逻辑从数据结构中分离出来,使其更加可维护和可扩展。
相关推荐
mldong3 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60615 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角6 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!6 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi7 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马9 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu9 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss