访问者模式(Visitor Pattern)
概念
访问者模式是一种行为设计模式,它允许你在不修改已有元素类的情况下,为元素类添加新的操作。访问者模式将数据结构与数据操作分离,将作用于某种数据结构中的各元素的操作封装在访问者类中,可以在不改变各元素类的前提下定义作用于这些元素的新操作。
访问者模式的核心思想是将数据结构和作用于结构上的操作解耦,使得操作集合可以相对自由地演化而不影响系统的数据结构。它适用于数据结构相对稳定的系统,通过访问者模式可以在不改变数据结构的前提下增加作用于这些元素的新操作。
基本实现
访问者模式包含以下主要角色:
- Visitor(抽象访问者):为该对象结构中的每个具体元素类声明一个访问操作。
- ConcreteVisitor(具体访问者):实现每个由Visitor声明的操作,确定访问者访问一个元素时该做什么。
- Element(抽象元素):定义一个accept操作,它以一个访问者为参数。
- ConcreteElement(具体元素):实现accept操作,通过调用访问者的访问方法来实现。
- ObjectStructure(对象结构):能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素。
下面是访问者模式的基本实现:
// 抽象访问者
class Visitor {
visitConcreteElementA(element) {
throw new Error('visitConcreteElementA method must be implemented');
}
visitConcreteElementB(element) {
throw new Error('visitConcreteElementB method must be implemented');
}
}
// 具体访问者A
class ConcreteVisitorA extends Visitor {
visitConcreteElementA(element) {
console.log(`具体访问者A访问 ${element.operationA()}`);
}
visitConcreteElementB(element) {
console.log(`具体访问者A访问 ${element.operationB()}`);
}
}
// 具体访问者B
class ConcreteVisitorB extends Visitor {
visitConcreteElementA(element) {
console.log(`具体访问者B访问 ${element.operationA()}`);
}
visitConcreteElementB(element) {
console.log(`具体访问者B访问 ${element.operationB()}`);
}
}
// 抽象元素
class Element {
accept(visitor) {
throw new Error('accept method must be implemented');
}
}
// 具体元素A
class ConcreteElementA extends Element {
accept(visitor) {
visitor.visitConcreteElementA(this);
}
operationA() {
return '具体元素A的操作';
}
}
// 具体元素B
class ConcreteElementB extends Element {
accept(visitor) {
visitor.visitConcreteElementB(this);
}
operationB() {
return '具体元素B的操作';
}
}
// 对象结构
class ObjectStructure {
constructor() {
this.elements = [];
}
attach(element) {
this.elements.push(element);
}
detach(element) {
const index = this.elements.indexOf(element);
if (index !== -1) {
this.elements.splice(index, 1);
}
}
accept(visitor) {
for (const element of this.elements) {
element.accept(visitor);
}
}
}
// 使用示例
const objectStructure = new ObjectStructure();
objectStructure.attach(new ConcreteElementA());
objectStructure.attach(new ConcreteElementB());
const visitorA = new ConcreteVisitorA();
const visitorB = new ConcreteVisitorB();
console.log('访问者A的访问:');
objectStructure.accept(visitorA);
console.log('\n访问者B的访问:');
objectStructure.accept(visitorB);实际应用场景
场景1:员工绩效考核系统
在人力资源管理系统中,不同类型的员工需要接受不同的绩效考核,使用访问者模式可以为员工添加新的考核方式而不修改员工类。
// 抽象访问者 - 绩效考核
class PerformanceReviewVisitor {
visitFullTimeEmployee(employee) {
throw new Error('visitFullTimeEmployee method must be implemented');
}
visitPartTimeEmployee(employee) {
throw new Error('visitPartTimeEmployee method must be implemented');
}
visitInternEmployee(employee) {
throw new Error('visitInternEmployee method must be implemented');
}
}
// 具体访问者 - 年度绩效考核
class AnnualReviewVisitor extends PerformanceReviewVisitor {
visitFullTimeEmployee(employee) {
console.log(`\n=== ${employee.getName()} 的年度绩效考核 ===`);
console.log(`员工类型: 全职员工`);
console.log(`工作年限: ${employee.getYearsOfService()} 年`);
console.log(`项目完成数: ${employee.getProjectsCompleted()} 个`);
// 计算年度评分
const score = Math.min(100,
employee.getProjectsCompleted() * 10 +
employee.getYearsOfService() * 5
);
console.log(`年度评分: ${score} 分`);
console.log(`考核结果: ${score >= 80 ? '优秀' : score >= 60 ? '合格' : '待改进'}`);
return score;
}
visitPartTimeEmployee(employee) {
console.log(`\n=== ${employee.getName()} 的年度绩效考核 ===`);
console.log(`员工类型: 兼职员工`);
console.log(`工作时长: ${employee.getHoursPerWeek()} 小时/周`);
console.log(`任务完成率: ${employee.getTaskCompletionRate()}%`);
// 计算年度评分
const score = Math.min(100, employee.getTaskCompletionRate());
console.log(`年度评分: ${score} 分`);
console.log(`考核结果: ${score >= 80 ? '优秀' : score >= 60 ? '合格' : '待改进'}`);
return score;
}
visitInternEmployee(employee) {
console.log(`\n=== ${employee.getName()} 的年度绩效考核 ===`);
console.log(`员工类型: 实习生`);
console.log(`实习时长: ${employee.getInternshipDuration()} 个月`);
console.log(`学习进度: ${employee.getLearningProgress()}%`);
console.log(`导师评价: ${employee.getMentorRating()} 分`);
// 计算年度评分
const score = Math.min(100,
employee.getLearningProgress() * 0.6 +
employee.getMentorRating() * 0.4
);
console.log(`年度评分: ${score} 分`);
console.log(`考核结果: ${score >= 80 ? '优秀' : score >= 60 ? '合格' : '待改进'}`);
return score;
}
}
// 具体访问者 - 薪资调整评估
class SalaryAdjustmentVisitor extends PerformanceReviewVisitor {
visitFullTimeEmployee(employee) {
console.log(`\n=== ${employee.getName()} 的薪资调整评估 ===`);
console.log(`当前薪资: ${employee.getSalary()} 元`);
// 基于绩效和市场情况评估薪资调整
const performanceScore = employee.getPerformanceScore() || 75;
let adjustmentPercentage = 0;
if (performanceScore >= 90) {
adjustmentPercentage = 15; // 优秀员工
} else if (performanceScore >= 80) {
adjustmentPercentage = 10; // 良好员工
} else if (performanceScore >= 70) {
adjustmentPercentage = 5; // 合格员工
} else {
adjustmentPercentage = 0; // 待改进员工
}
const newSalary = employee.getSalary() * (1 + adjustmentPercentage / 100);
console.log(`绩效评分: ${performanceScore} 分`);
console.log(`调整幅度: ${adjustmentPercentage}%`);
console.log(`调整后薪资: ${newSalary.toFixed(2)} 元`);
return {
oldSalary: employee.getSalary(),
newSalary: newSalary,
adjustment: adjustmentPercentage
};
}
visitPartTimeEmployee(employee) {
console.log(`\n=== ${employee.getName()} 的薪资调整评估 ===`);
console.log(`当前时薪: ${employee.getHourlyRate()} 元/小时`);
// 兼职员工薪资调整逻辑
const performanceScore = employee.getPerformanceScore() || 70;
let adjustmentPercentage = 0;
if (performanceScore >= 90) {
adjustmentPercentage = 10;
} else if (performanceScore >= 80) {
adjustmentPercentage = 5;
} else if (performanceScore >= 70) {
adjustmentPercentage = 2;
}
const newHourlyRate = employee.getHourlyRate() * (1 + adjustmentPercentage / 100);
console.log(`绩效评分: ${performanceScore} 分`);
console.log(`调整幅度: ${adjustmentPercentage}%`);
console.log(`调整后时薪: ${newHourlyRate.toFixed(2)} 元/小时`);
return {
oldHourlyRate: employee.getHourlyRate(),
newHourlyRate: newHourlyRate,
adjustment: adjustmentPercentage
};
}
visitInternEmployee(employee) {
console.log(`\n=== ${employee.getName()} 的转正评估 ===`);
// 实习生转正评估
const performanceScore = employee.getPerformanceScore() || 70;
const learningProgress = employee.getLearningProgress();
const canConvert = performanceScore >= 80 && learningProgress >= 85;
console.log(`绩效评分: ${performanceScore} 分`);
console.log(`学习进度: ${learningProgress}%`);
console.log(`转正建议: ${canConvert ? '建议转正' : '继续实习'}`);
return {
canConvert: canConvert,
reason: canConvert ? '符合转正条件' : '需要进一步提升'
};
}
}
// 抽象员工元素
class Employee {
constructor(name) {
this.name = name;
this.performanceScore = null;
}
getName() {
return this.name;
}
setPerformanceScore(score) {
this.performanceScore = score;
}
getPerformanceScore() {
return this.performanceScore;
}
accept(visitor) {
throw new Error('accept method must be implemented');
}
}
// 全职员工
class FullTimeEmployee extends Employee {
constructor(name, salary, yearsOfService, projectsCompleted) {
super(name);
this.salary = salary;
this.yearsOfService = yearsOfService;
this.projectsCompleted = projectsCompleted;
}
getSalary() {
return this.salary;
}
getYearsOfService() {
return this.yearsOfService;
}
getProjectsCompleted() {
return this.projectsCompleted;
}
accept(visitor) {
return visitor.visitFullTimeEmployee(this);
}
}
// 兼职员工
class PartTimeEmployee extends Employee {
constructor(name, hourlyRate, hoursPerWeek, taskCompletionRate) {
super(name);
this.hourlyRate = hourlyRate;
this.hoursPerWeek = hoursPerWeek;
this.taskCompletionRate = taskCompletionRate;
}
getHourlyRate() {
return this.hourlyRate;
}
getHoursPerWeek() {
return this.hoursPerWeek;
}
getTaskCompletionRate() {
return this.taskCompletionRate;
}
accept(visitor) {
return visitor.visitPartTimeEmployee(this);
}
}
// 实习生
class InternEmployee extends Employee {
constructor(name, internshipDuration, learningProgress, mentorRating) {
super(name);
this.internshipDuration = internshipDuration;
this.learningProgress = learningProgress;
this.mentorRating = mentorRating;
}
getInternshipDuration() {
return this.internshipDuration;
}
getLearningProgress() {
return this.learningProgress;
}
getMentorRating() {
return this.mentorRating;
}
accept(visitor) {
return visitor.visitInternEmployee(this);
}
}
// 员工管理系统(对象结构)
class EmployeeManagementSystem {
constructor() {
this.employees = [];
}
addEmployee(employee) {
this.employees.push(employee);
}
removeEmployee(employee) {
const index = this.employees.indexOf(employee);
if (index !== -1) {
this.employees.splice(index, 1);
}
}
accept(visitor) {
const results = [];
for (const employee of this.employees) {
const result = employee.accept(visitor);
results.push({
employee: employee.getName(),
result: result
});
}
return results;
}
}
// 使用示例
const ems = new EmployeeManagementSystem();
// 添加员工
const emp1 = new FullTimeEmployee('张三', 15000, 3, 8);
const emp2 = new FullTimeEmployee('李四', 12000, 2, 5);
const emp3 = new PartTimeEmployee('王五', 50, 20, 95);
const emp4 = new PartTimeEmployee('赵六', 60, 15, 85);
const emp5 = new InternEmployee('钱七', 6, 90, 85);
const emp6 = new InternEmployee('孙八', 3, 70, 75);
ems.addEmployee(emp1);
ems.addEmployee(emp2);
ems.addEmployee(emp3);
ems.addEmployee(emp4);
ems.addEmployee(emp5);
ems.addEmployee(emp6);
console.log('=== 员工绩效考核系统 ===');
// 年度绩效考核
console.log('\n--- 年度绩效考核 ---');
const annualResults = ems.accept(new AnnualReviewVisitor());
// 保存绩效评分
annualResults.forEach((result, index) => {
const employee = ems.employees[index];
employee.setPerformanceScore(result.result);
});
// 薪资调整评估
console.log('\n--- 薪资调整评估 ---');
const salaryResults = ems.accept(new SalaryAdjustmentVisitor());
// 显示统计信息
console.log('\n=== 考核统计 ===');
const fullTimeCount = ems.employees.filter(e => e instanceof FullTimeEmployee).length;
const partTimeCount = ems.employees.filter(e => e instanceof PartTimeEmployee).length;
const internCount = ems.employees.filter(e => e instanceof InternEmployee).length;
console.log(`全职员工: ${fullTimeCount} 人`);
console.log(`兼职员工: ${partTimeCount} 人`);
console.log(`实习生: ${internCount} 人`);场景2:文档处理系统
在文档处理系统中,不同类型的文档元素(段落、表格、图片等)需要支持多种操作(渲染、导出、统计等),使用访问者模式可以方便地为这些元素添加新操作。
// 抽象访问者
class DocumentVisitor {
visitParagraph(paragraph) {
throw new Error('visitParagraph method must be implemented');
}
visitTable(table) {
throw new Error('visitTable method must be implemented');
}
visitImage(image) {
throw new Error('visitImage method must be implemented');
}
visitHeading(heading) {
throw new Error('visitHeading method must be implemented');
}
}
// 具体访问者 - HTML渲染
class HTMLRenderer extends DocumentVisitor {
constructor() {
super();
this.html = '';
}
visitParagraph(paragraph) {
this.html += `<p>${paragraph.getText()}</p>\n`;
}
visitTable(table) {
this.html += '<table border="1">\n';
table.getRows().forEach(row => {
this.html += ' <tr>\n';
row.forEach(cell => {
this.html += ` <td>${cell}</td>\n`;
});
this.html += ' </tr>\n';
});
this.html += '</table>\n';
}
visitImage(image) {
this.html += `<img src="${image.getSrc()}" alt="${image.getAlt()}">\n`;
}
visitHeading(heading) {
this.html += `<h${heading.getLevel()}>${heading.getText()}</h${heading.getLevel()}>\n`;
}
getHTML() {
return this.html;
}
clear() {
this.html = '';
}
}
// 具体访问者 - 文本统计
class TextStatisticsVisitor extends DocumentVisitor {
constructor() {
super();
this.wordCount = 0;
this.characterCount = 0;
this.imageCount = 0;
this.tableCount = 0;
}
visitParagraph(paragraph) {
const text = paragraph.getText();
this.wordCount += text.split(/\s+/).filter(word => word.length > 0).length;
this.characterCount += text.length;
}
visitTable(table) {
this.tableCount++;
const rows = table.getRows();
this.characterCount += rows.flat().join('').length;
}
visitImage(image) {
this.imageCount++;
}
visitHeading(heading) {
const text = heading.getText();
this.wordCount += text.split(/\s+/).filter(word => word.length > 0).length;
this.characterCount += text.length;
}
getStatistics() {
return {
wordCount: this.wordCount,
characterCount: this.characterCount,
imageCount: this.imageCount,
tableCount: this.tableCount
};
}
clear() {
this.wordCount = 0;
this.characterCount = 0;
this.imageCount = 0;
this.tableCount = 0;
}
}
// 具体访问者 - Markdown导出
class MarkdownExporter extends DocumentVisitor {
constructor() {
super();
this.markdown = '';
}
visitParagraph(paragraph) {
this.markdown += `${paragraph.getText()}\n\n`;
}
visitTable(table) {
const rows = table.getRows();
if (rows.length > 0) {
// 表头
this.markdown += '| ' + rows[0].join(' | ') + ' |\n';
// 分隔行
this.markdown += '| ' + rows[0].map(() => '---').join(' | ') + ' |\n';
// 数据行
for (let i = 1; i < rows.length; i++) {
this.markdown += '| ' + rows[i].join(' | ') + ' |\n';
}
this.markdown += '\n';
}
}
visitImage(image) {
this.markdown += `})\n\n`;
}
visitHeading(heading) {
const level = '#'.repeat(heading.getLevel());
this.markdown += `${level} ${heading.getText()}\n\n`;
}
getMarkdown() {
return this.markdown;
}
clear() {
this.markdown = '';
}
}
// 抽象文档元素
class DocumentElement {
accept(visitor) {
throw new Error('accept method must be implemented');
}
}
// 段落元素
class Paragraph extends DocumentElement {
constructor(text) {
super();
this.text = text;
}
getText() {
return this.text;
}
accept(visitor) {
visitor.visitParagraph(this);
}
}
// 标题元素
class Heading extends DocumentElement {
constructor(text, level) {
super();
this.text = text;
this.level = level; // 1-6
}
getText() {
return this.text;
}
getLevel() {
return this.level;
}
accept(visitor) {
visitor.visitHeading(this);
}
}
// 表格元素
class Table extends DocumentElement {
constructor(rows) {
super();
this.rows = rows; // 二维数组
}
getRows() {
return this.rows;
}
accept(visitor) {
visitor.visitTable(this);
}
}
// 图片元素
class Image extends DocumentElement {
constructor(src, alt) {
super();
this.src = src;
this.alt = alt;
}
getSrc() {
return this.src;
}
getAlt() {
return this.alt;
}
accept(visitor) {
visitor.visitImage(this);
}
}
// 文档类(对象结构)
class Document {
constructor(title) {
this.title = title;
this.elements = [];
}
addElement(element) {
this.elements.push(element);
}
removeElement(element) {
const index = this.elements.indexOf(element);
if (index !== -1) {
this.elements.splice(index, 1);
}
}
accept(visitor) {
for (const element of this.elements) {
element.accept(visitor);
}
}
getTitle() {
return this.title;
}
}
// 使用示例
const document = new Document('访问者模式介绍');
// 添加文档元素
document.addElement(new Heading('访问者模式', 1));
document.addElement(new Paragraph('访问者模式是一种行为设计模式,它允许你在不修改已有元素类的情况下,为元素类添加新的操作。'));
document.addElement(new Heading('主要优点', 2));
document.addElement(new Paragraph('访问者模式的主要优点包括:'));
document.addElement(new Table([
['优点', '说明'],
['符合单一职责原则', '操作和数据结构分离'],
['优秀的扩展性', '可以在不修改对象结构的前提下定义新操作'],
['灵活性', '可以对对象结构中的元素进行不同的操作']
]));
document.addElement(new Heading('使用场景', 2));
document.addElement(new Paragraph('访问者模式适用于以下场景:'));
document.addElement(new Table([
['场景', '描述'],
['对象结构稳定', '系统中对象结构很少改变'],
['需要定义新操作', '需要对对象结构中的元素定义新操作'],
['操作复杂', '操作涉及多个元素类的复杂行为']
]));
document.addElement(new Image('visitor-pattern.png', '访问者模式结构图'));
document.addElement(new Paragraph('以上就是访问者模式的基本介绍和应用。'));
console.log('=== 文档处理系统演示 ===');
// HTML渲染
console.log('\n--- HTML渲染 ---');
const htmlRenderer = new HTMLRenderer();
document.accept(htmlRenderer);
console.log(htmlRenderer.getHTML());
// 文本统计
console.log('\n--- 文本统计 ---');
const statsVisitor = new TextStatisticsVisitor();
document.accept(statsVisitor);
const stats = statsVisitor.getStatistics();
console.log(`单词数: ${stats.wordCount}`);
console.log(`字符数: ${stats.characterCount}`);
console.log(`图片数: ${stats.imageCount}`);
console.log(`表格数: ${stats.tableCount}`);
// Markdown导出
console.log('\n--- Markdown导出 ---');
const markdownExporter = new MarkdownExporter();
document.accept(markdownExporter);
console.log(markdownExporter.getMarkdown());场景3:电商平台商品价格计算
在电商系统中,不同类型的商品需要支持多种价格计算方式(折扣、税费、运费等),使用访问者模式可以灵活地为商品添加新的价格计算逻辑。
// 抽象访问者 - 价格计算器
class PriceCalculator {
visitPhysicalProduct(product) {
throw new Error('visitPhysicalProduct method must be implemented');
}
visitDigitalProduct(product) {
throw new Error('visitDigitalProduct method must be implemented');
}
visitServiceProduct(product) {
throw new Error('visitServiceProduct method must be implemented');
}
}
// 具体访问者 - 折扣计算器
class DiscountCalculator extends PriceCalculator {
constructor(discountRate = 0) {
super();
this.discountRate = discountRate;
}
visitPhysicalProduct(product) {
const basePrice = product.getPrice();
const discount = basePrice * this.discountRate;
const finalPrice = basePrice - discount;
console.log(`${product.getName()} (实物商品)`);
console.log(` 原价: ¥${basePrice.toFixed(2)}`);
console.log(` 折扣: ¥${discount.toFixed(2)} (${this.discountRate * 100}%)`);
console.log(` 折后价: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
visitDigitalProduct(product) {
const basePrice = product.getPrice();
// 数字商品折扣率更高
const effectiveDiscount = Math.min(0.3, this.discountRate + 0.1);
const discount = basePrice * effectiveDiscount;
const finalPrice = basePrice - discount;
console.log(`${product.getName()} (数字商品)`);
console.log(` 原价: ¥${basePrice.toFixed(2)}`);
console.log(` 折扣: ¥${discount.toFixed(2)} (${effectiveDiscount * 100}%)`);
console.log(` 折后价: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
visitServiceProduct(product) {
const basePrice = product.getPrice();
// 服务商品折扣率较低
const effectiveDiscount = Math.max(0, this.discountRate - 0.05);
const discount = basePrice * effectiveDiscount;
const finalPrice = basePrice - discount;
console.log(`${product.getName()} (服务商品)`);
console.log(` 原价: ¥${basePrice.toFixed(2)}`);
console.log(` 折扣: ¥${discount.toFixed(2)} (${effectiveDiscount * 100}%)`);
console.log(` 折后价: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
}
// 具体访问者 - 税费计算器
class TaxCalculator extends PriceCalculator {
constructor(taxRate = 0.13) {
super();
this.taxRate = taxRate;
}
visitPhysicalProduct(product) {
const basePrice = product.getPrice();
const tax = basePrice * this.taxRate;
const finalPrice = basePrice + tax;
console.log(`${product.getName()} (实物商品)`);
console.log(` 商品价格: ¥${basePrice.toFixed(2)}`);
console.log(` 税费: ¥${tax.toFixed(2)} (${this.taxRate * 100}%)`);
console.log(` 含税价格: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
visitDigitalProduct(product) {
const basePrice = product.getPrice();
// 数字商品税率较低
const effectiveTax = this.taxRate * 0.3;
const tax = basePrice * effectiveTax;
const finalPrice = basePrice + tax;
console.log(`${product.getName()} (数字商品)`);
console.log(` 商品价格: ¥${basePrice.toFixed(2)}`);
console.log(` 税费: ¥${tax.toFixed(2)} (${effectiveTax * 100}%)`);
console.log(` 含税价格: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
visitServiceProduct(product) {
const basePrice = product.getPrice();
// 服务商品税率较高
const effectiveTax = this.taxRate * 1.5;
const tax = basePrice * effectiveTax;
const finalPrice = basePrice + tax;
console.log(`${product.getName()} (服务商品)`);
console.log(` 商品价格: ¥${basePrice.toFixed(2)}`);
console.log(` 税费: ¥${tax.toFixed(2)} (${effectiveTax * 100}%)`);
console.log(` 含税价格: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
}
// 具体访问者 - 运费计算器
class ShippingCalculator extends PriceCalculator {
constructor(shippingCost = 10) {
super();
this.shippingCost = shippingCost;
}
visitPhysicalProduct(product) {
const basePrice = product.getPrice();
const weight = product.getWeight() || 1;
const shipping = this.shippingCost * weight;
const finalPrice = basePrice + shipping;
console.log(`${product.getName()} (实物商品)`);
console.log(` 商品价格: ¥${basePrice.toFixed(2)}`);
console.log(` 运费: ¥${shipping.toFixed(2)} (重量: ${weight}kg)`);
console.log(` 含运费价格: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
visitDigitalProduct(product) {
const basePrice = product.getPrice();
// 数字商品无运费
console.log(`${product.getName()} (数字商品)`);
console.log(` 商品价格: ¥${basePrice.toFixed(2)}`);
console.log(` 运费: ¥0.00 (数字商品)`);
console.log(` 含运费价格: ¥${basePrice.toFixed(2)}`);
return basePrice;
}
visitServiceProduct(product) {
const basePrice = product.getPrice();
// 服务商品运费逻辑
const shipping = product.isRemoteService() ? 0 : this.shippingCost;
const finalPrice = basePrice + shipping;
console.log(`${product.getName()} (服务商品)`);
console.log(` 商品价格: ¥${basePrice.toFixed(2)}`);
console.log(` 运费: ¥${shipping.toFixed(2)} (${product.isRemoteService() ? '远程服务' : '上门服务'})`);
console.log(` 含运费价格: ¥${finalPrice.toFixed(2)}`);
return finalPrice;
}
}
// 抽象商品元素
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
getName() {
return this.name;
}
getPrice() {
return this.price;
}
accept(visitor) {
throw new Error('accept method must be implemented');
}
}
// 实物商品
class PhysicalProduct extends Product {
constructor(name, price, weight) {
super(name, price);
this.weight = weight;
}
getWeight() {
return this.weight;
}
accept(visitor) {
return visitor.visitPhysicalProduct(this);
}
}
// 数字商品
class DigitalProduct extends Product {
constructor(name, price, downloadUrl) {
super(name, price);
this.downloadUrl = downloadUrl;
}
getDownloadUrl() {
return this.downloadUrl;
}
accept(visitor) {
return visitor.visitDigitalProduct(this);
}
}
// 服务商品
class ServiceProduct extends Product {
constructor(name, price, duration, remoteService) {
super(name, price);
this.duration = duration; // 服务时长(小时)
this.remoteService = remoteService; // 是否远程服务
}
getDuration() {
return this.duration;
}
isRemoteService() {
return this.remoteService;
}
accept(visitor) {
return visitor.visitServiceProduct(this);
}
}
// 购物车(对象结构)
class ShoppingCart {
constructor() {
this.products = [];
}
addProduct(product) {
this.products.push(product);
}
removeProduct(product) {
const index = this.products.indexOf(product);
if (index !== -1) {
this.products.splice(index, 1);
}
}
accept(visitor) {
const results = [];
for (const product of this.products) {
const result = product.accept(visitor);
results.push({
product: product.getName(),
result: result
});
}
return results;
}
getTotalProducts() {
return this.products.length;
}
}
// 使用示例
const cart = new ShoppingCart();
// 添加商品
const product1 = new PhysicalProduct('iPhone 15', 5999, 0.2);
const product2 = new PhysicalProduct('MacBook Pro', 12999, 1.4);
const product3 = new DigitalProduct('Photoshop CC', 1599, 'https://adobe.com/photoshop');
const product4 = new DigitalProduct('Office 365', 399, 'https://microsoft.com/office');
const product5 = new ServiceProduct('家庭保洁', 299, 3, false);
const product6 = new ServiceProduct('在线编程辅导', 199, 1, true);
cart.addProduct(product1);
cart.addProduct(product2);
cart.addProduct(product3);
cart.addProduct(product4);
cart.addProduct(product5);
cart.addProduct(product6);
console.log('=== 电商平台价格计算系统 ===');
console.log(`购物车商品总数: ${cart.getTotalProducts()}\n`);
// 折扣计算(全场8折)
console.log('--- 折扣价格计算 (8折) ---');
const discountCalculator = new DiscountCalculator(0.2);
const discountResults = cart.accept(discountCalculator);
// 保存折后价格用于后续计算
const discountedPrices = {};
discountResults.forEach((result, index) => {
discountedPrices[cart.products[index].getName()] = result.result;
});
// 税费计算
console.log('\n--- 税费计算 ---');
const taxCalculator = new TaxCalculator(0.13);
cart.accept(taxCalculator);
// 运费计算
console.log('\n--- 运费计算 ---');
const shippingCalculator = new ShippingCalculator(15);
cart.accept(shippingCalculator);
// 综合计算示例
console.log('\n--- 综合价格计算示例 ---');
console.log('以 MacBook Pro 为例:');
const macbook = cart.products[1];
console.log('\n1. 原价:');
console.log(` ¥${macbook.getPrice().toFixed(2)}`);
console.log('\n2. 折后价 (8折):');
const discountedPrice = macbook.accept(new DiscountCalculator(0.2));
console.log(` ¥${discountedPrice.toFixed(2)}`);
console.log('\n3. 含税价格:');
const taxIncludedPrice = macbook.accept(new TaxCalculator(0.13));
console.log(` ¥${taxIncludedPrice.toFixed(2)}`);
console.log('\n4. 含运费价格:');
const shippingIncludedPrice = macbook.accept(new ShippingCalculator(15));
console.log(` ¥${shippingIncludedPrice.toFixed(2)}`);
console.log('\n5. 最终价格 (折后+税费+运费):');
const finalDiscountedPrice = macbook.accept(new DiscountCalculator(0.2));
const finalTaxCalculator = new TaxCalculator(0.13);
// 这里简化处理,实际应该基于折后价计算税费
const finalTax = finalDiscountedPrice * 0.13;
const finalShipping = macbook.getWeight() * 15;
const finalPrice = finalDiscountedPrice + finalTax + finalShipping;
console.log(` 折后价: ¥${finalDiscountedPrice.toFixed(2)}`);
console.log(` 税费: ¥${finalTax.toFixed(2)}`);
console.log(` 运费: ¥${finalShipping.toFixed(2)}`);
console.log(` 最终价格: ¥${finalPrice.toFixed(2)}`);关键要点
双重分派:访问者模式使用双重分派机制,首先通过元素的accept方法接收访问者,然后通过访问者的visit方法访问具体元素。
数据结构稳定:访问者模式适用于数据结构相对稳定的场景,如果数据结构经常变化,则不适合使用访问者模式。
操作可扩展:可以在不修改元素类的前提下增加新的操作,符合开闭原则。
违反封装性:访问者模式要求元素类暴露内部状态给访问者,可能违反封装性原则。
与其他模式的关系
- 与组合模式结合:访问者模式常与组合模式一起使用,访问者可以遍历组合对象结构。
- 与迭代器模式结合:访问者模式可以使用迭代器模式来遍历对象结构。
- 与策略模式区别:策略模式封装算法,而访问者模式封装作用于对象结构的操作。
优缺点
优点:
- 扩展性好:可以在不修改对象结构的前提下定义新操作。
- 符合单一职责原则:每个访问者都专注于完成特定的操作。
- 灵活性高:可以对对象结构中的元素进行不同的操作。
缺点:
- 违反封装性:访问者模式要求元素类暴露内部状态给访问者。
- 增加系统复杂性:每增加一个元素类都需要在访问者中增加相应的方法。
- 对象结构变化困难:如果对象结构经常变化,维护访问者会变得困难。
访问者模式在需要对复杂对象结构进行多种操作的场景中非常有用,特别是在编译器、文档处理、电商系统等领域有广泛应用。通过合理使用访问者模式,可以在不修改现有代码的前提下为系统增加新的功能,提高系统的可维护性和可扩展性。