什么是访问者模式?JavaScript 实现一个访问者模式的示例
·
什么是访问者模式?
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不修改现有类的前提下,为一组不同类型的对象添加新的操作。
其核心思想是:将对象的 “数据结构” 与 “操作行为” 分离,通过一个 “访问者” 对象封装对数据结构的操作,不同的访问者可以实现不同的操作逻辑,而数据结构中的对象只需提供一个接收访问者的接口。
访问者模式的核心角色
- 访问者(Visitor):定义对不同元素(数据结构中的对象)的访问操作接口,每个操作对应一种元素类型。
- 具体访问者(ConcreteVisitor):实现访问者接口,提供对每种元素的具体操作逻辑。
- 元素(Element):定义接收访问者的接口(如
accept(visitor)),所有具体元素必须实现该接口。 - 具体元素(ConcreteElement):实现元素接口,在
accept方法中调用访问者对应的操作(通常是visitor.visit(this))。 - 对象结构(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元
代码说明
-
访问者(PriceVisitor):定义了对两种商品(
Book、Electronic)的访问接口(visitBook、visitElectronic),每种接口对应一种元素的操作。 -
具体访问者:
OriginalPriceVisitor:实现原价计算逻辑,直接返回商品价格。DiscountPriceVisitor:实现折扣价计算(书籍 9 折、电子产品 8 折)。TaxPriceVisitor:实现税后价计算(统一加 10% 税)。每个访问者封装了一种独立的操作逻辑,与商品类完全解耦。
-
元素(Product):定义
accept方法,规定所有商品必须能接收访问者。 -
具体元素(Book、Electronic):
- 在
accept方法中,主动调用访问者对应的方法(如Book调用visitor.visitBook(this)),将自身传递给访问者,这是访问者模式的关键(“双分派” 机制)。
- 在
-
对象结构(Order):管理商品集合,提供
accept方法遍历所有商品,让访问者批量访问,简化客户端操作。
访问者模式的核心优势
- 分离数据与操作:商品类(数据结构)只负责存储数据,价格计算(操作)封装在访问者中,修改或新增操作无需修改商品类,符合 “开闭原则”。
- 灵活扩展操作:新增价格计算方式(如会员价),只需添加新的访问者(
MemberPriceVisitor),无需改动现有代码。 - 集中管理相关操作:同一类操作(如所有价格计算)集中在访问者中,便于维护(如统一调整税率)。
适用场景与局限性
- 适用场景:
- 数据结构稳定,但需要频繁添加新操作(如报表生成、数据导出、多维度计算)。
- 数据结构包含多种不同类型的对象(如订单中的商品、优惠券、运费),需要对每种类型执行不同操作。
- 局限性:
- 数据结构变动困难:若新增商品类型(如
Clothes),需修改所有访问者接口和实现,违反 “开闭原则”。 - 破坏封装性:访问者需要访问元素的内部状态(如
book.price),可能暴露元素的私有信息。
- 数据结构变动困难:若新增商品类型(如
访问者模式的核心是 “操作与数据分离,扩展操作灵活”,它通过访问者对象封装操作逻辑,使系统在数据结构稳定时能轻松扩展新功能,适合处理多类型对象的复杂操作场景。
更多推荐


所有评论(0)