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

news2024/12/28 22:56:46

一. 前言

在面向对象编程中,访问者模式(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 = left
    this.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. 缺点

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

  • 增加了许多新的类。

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

六. 总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2212447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【硬件模块】HC-08蓝牙模块

蓝牙模块型号 HC-08蓝牙模块实物图 HC-08蓝牙模块引脚介绍 STATE:状态输出引脚。未连接时,则为低电平。连接成功时,则为高电平。可以在程序中作指示引脚使用; RXD:串口接收引脚。接单片机的 TX 引脚(如…

多线程(三):线程等待获取线程引用线程休眠线程状态

目录 1、等待一个线程:join 1.1 join() 1.2 join(long millis)——"超时时间" 1.3 join(long millis,int nanos) 2、获取当前线程的引用:currentThread 3、休眠当前进程:sleep 3.1 实际休眠时间 3.2 sleep的特殊…

Canvas:AI协作的新维度

在人工智能的浪潮中,OpenAI的最新力作Canvas,不仅是一款新工具,它标志着人工智能协作方式的一次革命性飞跃。Canvas为写作和编程提供了一个全新的交互界面,让用户能够与ChatGPT进行更紧密、更直观的协作。 ​​​​​​​ Canvas的…

LeetCode 面试经典150题 Z字形变换

题目: 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下: P A H N A P L S I I G Y I R 之后,你…

无人机无线电侦测核心技术详解

无人机无线电侦测核心技术主要涉及频谱分析、信号处理、定位技术以及智能化识别等多个方面。以下是对这些核心技术的详细解析: 1. 频谱分析技术 频谱探测技术:通过分析信号在频域上的分布和特性,来识别、测量和定位无线电信号。在无人机侦测…

leetcode 491.非递减子序列

1.题目要求: 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。 …

Python | Leetcode Python题解之第477题汉明距离总和

题目: 题解: class Solution:def totalHammingDistance(self, nums: List[int]) -> int:n len(nums)ans 0for i in range(30):c sum(((val >> i) & 1) for val in nums)ans c * (n - c)return ans

BUUCTF-Business Planning Group1

下载是一张PNG图片, 使用010editor打开发现尾部有隐藏文件 把这段16进制字符串复制到新文件并改名为1.bpg 使用bpgviewer软件查看 Release Simple BPG Image viewer asimba/pybpgviewer GitHub Notes: large images will be scaled to fit screen areabasic s…

[单master节点k8s部署]40.安装harbor

harbor 是私有镜像仓库,用来存储和分发镜像的 。docker 还有一个官方的镜像仓库 docker hub,免费用户只能简单的使用,创建一个私有镜像仓库,存储镜像,付费用户才可以拥有更多权限,默认 docker pull 拉取镜像…

nginx搭建视频下载站

目录 1.前言与介绍 2.atuoindex模块介绍 3.主要的功能 4.下载站基本要求 5.具体配置 6.做完如上的配置重载服务 7.创建密码文件 8.在windows主机上做域名解析 9.查看统计结果 1.前言与介绍 前面的学习中根据nginx的简单的配置实现了根据 IP 端口 和 域名 实现虚…

MySQL-三范式 视图

文章目录 三范式三范式简介第一范式第二范式第三范式 表设计一对一一对多多对多最终的设计 视图 三范式 三范式简介 所谓三范式, 其实是表设计的三大原则, 目的都是为了节省空间, 但是三范式是必须要遵守的吗? 答案是否定的(但是第一范式必须遵守) 因为有时候严格遵守三范式…

AI开发-三方库-Hugging Face-Tokenizer

1 需求 需求1:from transformers import AutoTokenizer 需求2:from transformers import BertTokenizer 2 接口 关键参数 textpaddingtruncationreturn_tensors 3 示例 BertTokenizer.from_pretrained() PreTrainedTokenizer PreTrainedTokenizerBa…

医疗图像之基于Unet++的息肉分割

第一步:准备数据 息肉分割数据,总共有1000张 第二步:搭建模型 UNet,这是一种旨在克服以上限制的新型通用图像分割体系结构。如下图所示,UNet由不同深度的U-Net组成,其解码器通过重新设计的跳接以相同的分…

CountUp.js 实现数字增长动画 Vue

效果&#xff1a; 官网介绍 1. 安装 npm install --save countup.js2. 基本使用 // template <span ref"number1Ref"></span>// script const number1Ref ref<HTMLElement>() onMounted(() > {new CountUp(number1Ref.value!, 9999999).sta…

C语言 | Leetcode C语言题解之第477题汉明距离总和

题目&#xff1a; 题解&#xff1a; int totalHammingDistance(int* nums, int numsSize) {int ans 0;for (int i 0; i < 30; i) {int c 0;for (int j 0; j < numsSize; j) {c (nums[j] >> i) & 1;}ans c * (numsSize - c);}return ans; }

超GPT3.5性能,无限长文本,超强RAG三件套,MiniCPM3-4B模型分享

MiniCPM3-4B是由面壁智能与清华大学自然语言处理实验室合作开发的一款高性能端侧AI模型&#xff0c;它是MiniCPM系列的第三代产品&#xff0c;具有4亿参数量。 MiniCPM3-4B模型在性能上超过了Phi-3.5-mini-Instruct和GPT-3.5-Turbo-0125&#xff0c;并且与多款70亿至90亿参数的…

元组与列表嵌套用法

1.可以对列表中的元素修改&#xff0c;不能对元组中的元素修改&#xff1b;当元组与列表嵌套时遵循上述原则. 下图为元组与列表的嵌套案例&#xff08;学生信息的完善&#xff09;&#xff1a;

QQ快捷键冲突解决方法

注意&#xff1a;快捷键被占用&#xff0c;更改快捷键后使用不了&#xff0c;是因为有其他系统快捷键被占用&#xff0c;多尝试几个就可以了

计算机是如何输入存储输出汉字、图片、音频、视频的

计算机是如何输入存储输出汉字、图片、音频、视频的 为了便于理解&#xff0c;先了解一下计算机的组成。 冯诺依曼计算机的五大组成部分。分别是运算器、控制器、存储器、输入设备和输出设备。参见下图&#xff1a; 一、运算器 运算器又称“算术逻辑单元”&#xff0c;是计算…

Golang | Leetcode Golang题解之第477题汉明距离总和

题目&#xff1a; 题解&#xff1a; func totalHammingDistance(nums []int) (ans int) {n : len(nums)for i : 0; i < 30; i {c : 0for _, val : range nums {c val >> i & 1}ans c * (n - c)}return }