什么是访问者模式?

访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不修改现有类的前提下,为一组不同类型的对象添加新的操作

其核心思想是:将对象的 “数据结构” 与 “操作行为” 分离,通过一个 “访问者” 对象封装对数据结构的操作,不同的访问者可以实现不同的操作逻辑,而数据结构中的对象只需提供一个接收访问者的接口。

访问者模式的核心角色

  1. 访问者(Visitor):定义对不同元素(数据结构中的对象)的访问操作接口,每个操作对应一种元素类型。
  2. 具体访问者(ConcreteVisitor):实现访问者接口,提供对每种元素的具体操作逻辑。
  3. 元素(Element):定义接收访问者的接口(如 accept(visitor)),所有具体元素必须实现该接口。
  4. 具体元素(ConcreteElement):实现元素接口,在 accept 方法中调用访问者对应的操作(通常是 visitor.visit(this))。
  5. 对象结构(ObjectStructure):存储元素的集合,提供遍历元素的方法,方便访问者批量访问所有元素。

JavaScript 实现访问者模式示例:商品价格计算

以下以 “电商订单商品处理” 为例,实现访问者模式。场景是:订单中有多种商品(如书籍、电子产品),需要计算不同类型的价格(原价、折扣价、税后价)。通过访问者模式,可在不修改商品类的情况下,灵活添加新的价格计算逻辑。

代码实现
// -------------- 1. 访问者(Visitor):价格计算接口 --------------
class PriceVisitor {
  // 访问书籍(具体元素1)
  visitBook(book) {
    throw new Error("子类必须实现 visitBook 方法");
  }

  // 访问电子产品(具体元素2)
  visitElectronic(electronic) {
    throw new Error("子类必须实现 visitElectronic 方法");
  }
}

// -------------- 2. 具体访问者:不同价格计算逻辑 --------------
// 计算原价
class OriginalPriceVisitor extends PriceVisitor {
  visitBook(book) {
    console.log(`书籍《${book.name}》原价:${book.price}元`);
    return book.price;
  }

  visitElectronic(electronic) {
    console.log(`电子产品${electronic.name}原价:${electronic.price}元`);
    return electronic.price;
  }
}

// 计算折扣价(书籍9折,电子产品8折)
class DiscountPriceVisitor extends PriceVisitor {
  visitBook(book) {
    const discountPrice = book.price * 0.9;
    console.log(`书籍《${book.name}》折扣价:${discountPrice.toFixed(2)}元(9折)`);
    return discountPrice;
  }

  visitElectronic(electronic) {
    const discountPrice = electronic.price * 0.8;
    console.log(`电子产品${electronic.name}折扣价:${discountPrice.toFixed(2)}元(8折)`);
    return discountPrice;
  }
}

// 计算税后价(税率10%)
class TaxPriceVisitor extends PriceVisitor {
  visitBook(book) {
    const taxPrice = book.price * 1.1;
    console.log(`书籍《${book.name}》税后价:${taxPrice.toFixed(2)}元(含税10%)`);
    return taxPrice;
  }

  visitElectronic(electronic) {
    const taxPrice = electronic.price * 1.1;
    console.log(`电子产品${electronic.name}税后价:${taxPrice.toFixed(2)}元(含税10%)`);
    return taxPrice;
  }
}

// -------------- 3. 元素(Element):商品接口 --------------
class Product {
  // 接收访问者
  accept(visitor) {
    throw new Error("子类必须实现 accept 方法");
  }
}

// -------------- 4. 具体元素:书籍和电子产品 --------------
class Book extends Product {
  constructor(name, price) {
    super();
    this.name = name;
    this.price = price;
  }

  // 接收访问者:调用访问者的 visitBook 方法
  accept(visitor) {
    return visitor.visitBook(this);
  }
}

class Electronic extends Product {
  constructor(name, price) {
    super();
    this.name = name;
    this.price = price;
  }

  // 接收访问者:调用访问者的 visitElectronic 方法
  accept(visitor) {
    return visitor.visitElectronic(this);
  }
}

// -------------- 5. 对象结构(ObjectStructure):订单(商品集合)--------------
class Order {
  constructor() {
    this.products = []; // 存储商品
  }

  // 添加商品
  addProduct(product) {
    this.products.push(product);
  }

  // 遍历所有商品,让访问者访问
  accept(visitor) {
    let total = 0;
    this.products.forEach(product => {
      total += product.accept(visitor); // 每个商品接收访问者
    });
    console.log(`总价:${total.toFixed(2)}元\n`);
    return total;
  }
}

// -------------- 6. 客户端使用 --------------
// 创建商品(具体元素)
const book1 = new Book("JavaScript 设计模式", 89);
const book2 = new Book("数据结构与算法", 69);
const phone = new Electronic("智能手机", 4999);
const laptop = new Electronic("笔记本电脑", 7999);

// 创建订单(对象结构)
const order = new Order();
order.addProduct(book1);
order.addProduct(book2);
order.addProduct(phone);
order.addProduct(laptop);

// 使用不同访问者计算价格
console.log("=== 计算原价 ===");
order.accept(new OriginalPriceVisitor());
// 输出:
// 书籍《JavaScript 设计模式》原价:89元
// 书籍《数据结构与算法》原价:69元
// 电子产品智能手机原价:4999元
// 电子产品笔记本电脑原价:7999元
// 总价:13156.00元

console.log("=== 计算折扣价 ===");
order.accept(new DiscountPriceVisitor());
// 输出:
// 书籍《JavaScript 设计模式》折扣价:80.10元(9折)
// 书籍《数据结构与算法》折扣价:62.10元(9折)
// 电子产品智能手机折扣价:3999.20元(8折)
// 电子产品笔记本电脑折扣价:6399.20元(8折)
// 总价:10540.60元

console.log("=== 计算税后价 ===");
order.accept(new TaxPriceVisitor());
// 输出:
// 书籍《JavaScript 设计模式》税后价:97.90元(含税10%)
// 书籍《数据结构与算法》税后价:75.90元(含税10%)
// 电子产品智能手机税后价:5498.90元(含税10%)
// 电子产品笔记本电脑税后价:8798.90元(含税10%)
// 总价:14471.60元

代码说明

  1. 访问者(PriceVisitor):定义了对两种商品(BookElectronic)的访问接口(visitBookvisitElectronic),每种接口对应一种元素的操作。

  2. 具体访问者

    • OriginalPriceVisitor:实现原价计算逻辑,直接返回商品价格。
    • DiscountPriceVisitor:实现折扣价计算(书籍 9 折、电子产品 8 折)。
    • TaxPriceVisitor:实现税后价计算(统一加 10% 税)。每个访问者封装了一种独立的操作逻辑,与商品类完全解耦。
  3. 元素(Product):定义 accept 方法,规定所有商品必须能接收访问者。

  4. 具体元素(Book、Electronic)

    • 在 accept 方法中,主动调用访问者对应的方法(如 Book 调用 visitor.visitBook(this)),将自身传递给访问者,这是访问者模式的关键(“双分派” 机制)。
  5. 对象结构(Order):管理商品集合,提供 accept 方法遍历所有商品,让访问者批量访问,简化客户端操作。

访问者模式的核心优势

  • 分离数据与操作:商品类(数据结构)只负责存储数据,价格计算(操作)封装在访问者中,修改或新增操作无需修改商品类,符合 “开闭原则”。
  • 灵活扩展操作:新增价格计算方式(如会员价),只需添加新的访问者(MemberPriceVisitor),无需改动现有代码。
  • 集中管理相关操作:同一类操作(如所有价格计算)集中在访问者中,便于维护(如统一调整税率)。

适用场景与局限性

  • 适用场景
    • 数据结构稳定,但需要频繁添加新操作(如报表生成、数据导出、多维度计算)。
    • 数据结构包含多种不同类型的对象(如订单中的商品、优惠券、运费),需要对每种类型执行不同操作。
  • 局限性
    • 数据结构变动困难:若新增商品类型(如 Clothes),需修改所有访问者接口和实现,违反 “开闭原则”。
    • 破坏封装性:访问者需要访问元素的内部状态(如 book.price),可能暴露元素的私有信息。

访问者模式的核心是 “操作与数据分离,扩展操作灵活”,它通过访问者对象封装操作逻辑,使系统在数据结构稳定时能轻松扩展新功能,适合处理多类型对象的复杂操作场景。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐