JavaScript 访问者模式:打造高扩展性的对象结构

news/2024/12/21 21:50:41/

一. 前言

在面向对象编程中,访问者模式Visitor Pattern)是一种行为设计模式,它允许我们向现有的类结构添加新的操作,而无需修改这些类。这对于需要对类层次结构中的元素进行复杂算法处理的场景非常有用。

本文将详细介绍访问者模式的概念、实现方式及其在 JavaScript 中的一个应用小案例,让我们更加快速的了解它。

二. 什么是访问者模式

1. 定义

访问者模式定义了一个访问者的接口,该接口可以处理一个对象结构中的各个元素,而无需改变各元素的类。使用访问者模式可以让用户在不修改现有对象结构的情况下定义新的操作。

2. 核心角色

  • Object Structure(对象结构):是一个包含一个或多个 Element 对象的集合,同时提供一个接受操作 accept(),以接收一个访问者对象。

  • Element(元素):是对象结构中每个对象的类,它提供一个 accept()方法以接收访问者。

  • Visitor(访问者):是一个接口,为 Element 类中的每一个具体类声明一个 Visit 方法。

  • Concrete Visitor(具体访问者):实现了 Visitor 接口中声明的各个 Visit 方法,每个方法实现了对应 Element 类型的业务逻辑。

3. UML

三. 实现方式

访问者模式的实现方式依赖于具体的应用场景和技术栈。在 JavaScript 中,实现访问者模式可以通过多种方式,其中最常见的方法是使用对象组合和多态来完成。

假设我们有一个简单的表达式树,其中包含两种节点类型:加法节点和数字节点。我们将使用访问者模式来计算表达式的值。

  1. 定义元素接口:首先,需要定义一个抽象的元素接口,该接口至少包含一个 accept 方法,此方法接受一个访问者作为参数。

// 元素接口
class Element {accept(visitor) {throw new Error('Method not implemented.')}
}
  1. 具体元素实现:为每种具体的元素类型实现该接口。每个具体元素类都必须实现 accept 方法,并调用访问者对应的访问方法。

// 具体元素 - 加法节点
class AddNode extends Element {constructor(left, right) {super()this.left = leftthis.right = right}accept(visitor) {return visitor.visitAddNode(this)}
}// 具体元素 - 数字节点
class NumberNode extends Element {constructor(value) {super()this.value = value}accept(visitor) {return visitor.visitNumberNode(this)}
}
  1. 定义访问者接口:定义一个访问者接口,该接口为每一种具体的元素类型声明一个访问方法。

// 访问者接口
class Visitor {visitAddNode(node) {throw new Error('Method not implemented.')}visitNumberNode(node) {throw new Error('Method not implemented.')}
}
  1. 具体访问者实现:为每种访问者类型实现具体的访问逻辑,每个具体访问者类都要实现所有访问方法。

// 具体访问者 - 表达式求值
class Evaluator extends Visitor {visitAddNode(node) {return this.visit(node.left) + this.visit(node.right)}visitNumberNode(node) {return node.value}visit(node) {return node.accept(this)}
}
  1. 对象结构:定义一个对象结构,它包含元素的集合,并提供方法来迭代这些元素,允许访问者访问它们。

// 对象结构
class ExpressionTree {constructor(root) {this.root = root}evaluate() {const evaluator = new Evaluator()return evaluator.visit(this.root)}
}
  1. 具体使用:最后,编写相应代码,实例化具体元素和访问者,并通过对象结构让访问者访问所有的元素。

// 使用
const tree = new ExpressionTree(new AddNode(new NumberNode(1), new AddNode(new NumberNode(2), new NumberNode(3))))console.log(tree.evaluate()) // 输出: 6

解释一下,在以上这几个步骤中:

  • AddNodeNumberNode 是具体元素,它们都实现了 accept 方法,调用访问者对应的 visitAddNodevisitNumberNode 方法。

  • Evaluator 是一个具体访问者,它实现了访问者接口中定义的 visitAddNodevisitNumberNode 方法,用于计算表达式的值。

  • ExpressionTree 是对象结构,它持有表达式的根节点,并提供 evaluate 方法来遍历树结构,执行计算。

这种方法的优点在于它允许我们在不修改现有元素类的情况下引入新的访问者类(例如,添加一个新的访问者来打印表达式的字符串表示形式)。这样就可以很容易地扩展系统的功能。

四. 实战应用

通过以上我们已经学习到的实现方式,接下来我们基于访问者模式来设计一个对象类型校验的方式。主要原理是:借用对象的 toString 方法。

我们使用访问者模式来创建一个类型检查器,它可以检查给定对象的类型,并根据对象的类型执行特定的操作。这样可以方便地扩展类型检查逻辑,而无需修改现有代码。

1. 定义元素接口

首先,我们需要定义一个抽象的元素基类,该类包含一个 accept 方法,用于接收访问者。

class Element {accept(visitor) {throw new Error('Method not implemented.')}
}

2. 具体元素

然后,我们定义具体的元素类,每个类都实现 accept 方法,并调用访问者对应的访问方法。

class StringElement extends Element {constructor(value) {super()this.value = value}accept(visitor) {visitor.visit(this)}
}class NumberElement extends Element {constructor(value) {super()this.value = value}accept(visitor) {visitor.visit(this)}
}class BooleanElement extends Element {constructor(value) {super()this.value = value}accept(visitor) {visitor.visit(this)}
}class ArrayElement extends Element {constructor(value) {super()this.value = value}accept(visitor) {visitor.visit(this)}
}class ObjectElement extends Element {constructor(value) {super()this.value = value}accept(visitor) {visitor.visit(this)}
}// 可以继续添加其他类型的元素

3. 定义访问者接口

接着,我们定义一个访问者接口,它为每一种具体的元素类型声明一个访问方法。

class Visitor {visit(element) {throw new Error('Method not implemented.')}
}

4. 具体访问者

然后,我们实现具体的访问者类,每个类都会实现访问者接口中的方法,并提供具体的功能实现。

class TypeChecker extends Visitor {visit(element) {const type = this.getType(element)console.log(`Type of '${element.value}' is ${type}.`)}getType(element) {const toString = Object.prototype.toString.call(element.value)switch (toString) {case '[object String]':return 'String'case '[object Number]':return 'Number'case '[object Boolean]':return 'Boolean'case '[object Array]':return 'Array'case '[object Object]':return 'Object'default:return 'Unknown'}}
}

5. 客户端代码

最后,编写客户端代码来实例化具体元素和访问者,并通过元素的 accept 方法让访问者访问所有元素。

// 创建具体元素
const stringElement = new StringElement('Hello')
const numberElement = new NumberElement(123)
const booleanElement = new BooleanElement(true)
const arrayElement = new ArrayElement([1, 2, 3])
const objectElement = new ObjectElement({ key: 'value' })// 创建访问者
const typeChecker = new TypeChecker()// 使用访问者检查所有元素的类型
stringElement.accept(typeChecker) // 输出: "Type of 'Hello' is String."
numberElement.accept(typeChecker) // 输出: "Type of '123' is Number."
booleanElement.accept(typeChecker) // 输出: "Type of 'true' is Boolean."
arrayElement.accept(typeChecker) // 输出: "Type of '1,2,3' is Array."
objectElement.accept(typeChecker) // 输出: "Type of '{ key: 'value' }' is Object."
  1. **元素基类 Element**:定义了一个 accept 方法,该方法用于接收访问者。

  2. 具体元素:如 StringElementNumberElement 等,它们都继承自 Element 并实现了 accept 方法,该方法会调用访问者中对应的方法。

  3. **访问者接口 Visitor**:定义了一个通用的 visit 方法,具体访问者需要实现这个方法。

  4. **具体访问者 TypeChecker**:实现了访问者接口中的方法,并根据元素类型执行特定的操作。getType 方法使用 Object.prototype.toString.call() 方法来确定对象的确切类型,并返回相应的类型名称。

通过这种方式,我们可以在不修改现有元素类的情况下添加新的类型检查逻辑。如果需要添加更多的类型检查功能,只需扩展 Visitor 接口并实现新的具体访问者类即可。这种方法不仅提高了代码的可扩展性,还使得代码更加模块化和易于维护。

在实际开发过程中,可能我们不会完全标准化的像上述这种方式来使用访问者模式,但是我们应该在编码过程中借用其中的思想,所以在前端开发中,大多数情况下我们使用的也都是访问者模式的变种,保证单一职责原则,不修改现有对象结构。

五. 优缺点

1. 优点

  • 让用户可以在不修改现有对象结构的情况下添加新功能。

  • 符合单一职责原则,分离了数据结构和作用于结构上的操作。

2. 缺点

通过以上了解到,缺点很明显:

  • 增加了许多新的类。

  • 如果对象结构中元素种类频繁变动,则访问者类也会频繁变动。

六. 总结

访问者模式通过定义一个访问者的接口以及一系列访问者类,使得可以在不修改现有对象结构的前提下为对象结构添加新的操作。

这种方式非常适合于需要对复杂的数据结构执行多种不同且不相关操作的场景。然而,这也意味着当对象结构发生变化时,访问者模式可能会变得复杂且难以维护。

更多 JavaScript 设计模式请关注我的专栏:JavaScript 设计模式专栏

 


http://www.ppmy.cn/news/1536944.html

相关文章

第五十九周周报 IAGNN

文章目录 week 59 IAGNN摘要Abstract一、大数据相关1. 磁盘扩容以及数据恢复2. 单机hbase 二、文献阅读1. 题目2. Abstract3. 网络结构3.1 问题定义3.2 IAGNN 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.4 实验结果 5. 结论参考文献 week 59 IAGNN 摘要 本周阅读了…

多模态简单了解

多模态 1.文本编码2. ViT图像编码器2.1图像矩阵self-attention计算: 3.Transformer多模态3.1CLIP 图文交互3.2 对比学习训练3.3 flamingo 图文交互3.4 LLava 图文交互 1.文本编码 简介: 即通过embedding将字符向量化,进入模型即可。 2. ViT…

selenium-Alert类用于操作提示框/确认弹框(4)

之前文章我们提到,在webdriver.WebDriver类有一个switch_to方法,通过switch_to.alert()可以返回Alert对象,而Alert对象主要用于网页中弹出的提示框/确认框/文本输入框的确认或者取消等动作。 Alert介绍 当在页面定位到提示框/确认框/文本录入…

XILINX MIG驱动

简介 框架图 本章节主要针对MIG读写做详细介绍,首先创建BLOCK DESIGN,工程连接如下图所示: MIG IP介绍 DATAMOVER的配置这里不再做介绍,结合上篇文章讲到DATAMOVER对BRAM进行读写操作,这里通过AXI桥再加一个MIG模块,MIG模块的配置和说明如下: 1、Clock Period:…

JAVA八股文1

1.Java 基础 1.1 语法基础 封装 利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内…

C++语言学习(10):《C++程序设计原理与实践》第五章笔记

第五章是「错误」,探讨了错误类型、错误来源、规避或解决方法(调试)。 错误的分类 编译时错误链接时错误运行时错误逻辑错误 错误的来源 找到错误的源头,然后去消除或解决错误;而不是从报错现场,逐层…

Dit架构 diffusion范式分类+应用

1.ping 网址 2.ssh nscc/l20 3.crtl,打开vscode的setting 4.win 10修改ssh配置文件及其密钥权限为600 - 晴云孤魂 - 博客园 整体来看: 使用transformer作为其主干网络,代替了原先的UNet 在latent space进行训练,通过transformer处理潜…

计算机毕业设计—基于python技术的机器学习、深度学习毕业设计选题的一些思考及参考

计算机毕业设计选题的重要性 毕业设计的选题至关重要,适合的选题将直接影响后续的论文撰写与答辩。不当的选题可能会带来一系列麻烦。 选题难易度 选题应在适当的难度范围内,既不能过于简单,也不可太难。选题太难可能导致知识储备不足&#…