1. 从“巨无霸”对象说起:为什么我们需要访问者模式?
三年前我接手过一个电商后台系统,核心的 Product 类足足有 1200 行代码。这个类不仅要管理商品基础信息,还包含了价格计算、库存预警、促销规则、日志记录等十几种功能。每次新增业务需求,开发团队都像在走钢丝——既要小心翼翼避免影响现有功能,又要在已经臃肿的类里继续塞代码。
这正是面向对象开发中最常见的反模式之一:上帝对象(God Object)。当我们将数据结构和业务逻辑强耦合在一起时,就会导致:
- 核心类变得脆弱:任何修改都可能引发连锁反应
- 测试成本指数级增长:每次改动都需要回归测试整个类
- 团队协作困难:多人修改同一类时频繁产生冲突
1.1 问题的本质:稳定与变化的对抗
在软件设计中存在一个基本矛盾:
- 数据结构通常是稳定的(如用户的基本属性、商品的核心字段)
- 业务逻辑却是频繁变化的(如新的计算规则、报表格式、业务流程)
当我们把变化的部分(业务逻辑)写入稳定的部分(数据结构)时,就违反了开闭原则(对扩展开放,对修改关闭)。访问者模式的本质,正是通过双重分发机制来解决这一矛盾。
关键洞察:就像现实生活中,房子(数据结构)不会频繁改动,但我们可以随时更换家具(业务逻辑)。访问者模式让我们的代码也遵循同样的智慧。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 访问者模式深度解析
2.1 经典实现:双分派机制
传统访问者模式包含两个核心组件:
- 被访问者(Element):定义
accept方法接收访问者 - 访问者(Visitor):为每个被访问者类型提供
visit方法
typescript复制// 被访问者接口
interface Employee {
accept(visitor: EmployeeVisitor): void;
}
// 具体被访问者
class Engineer implements Employee {
constructor(public name: string, public salary: number) {}
accept(visitor: EmployeeVisitor) {
visitor.visitEngineer(this);
}
}
// 访问者接口
interface EmployeeVisitor {
visitEngineer(engineer: Engineer): void;
visitManager(manager: Manager): void;
}
// 具体访问者
class BonusCalculator implements EmployeeVisitor {
visitEngineer(engineer: Engineer) {
console.log(`${engineer.name} 的奖金: ${engineer.salary * 0.2}`);
}
visitManager(manager: Manager) {
console.log(`${manager.name} 的奖金: ${manager.salary * 0.5}`);
}
}
这种实现虽然严谨,但在动态类型语言中显得过于繁琐。下面我们看更符合JavaScript习惯的实现方式。
2.2 JavaScript优化版:策略表模式
针对JS的特性,我们可以简化实现:
javascript复制// 策略表访问者
const visitors = {
bonus: {
Engineer: emp => emp.salary * 0.2,
Manager: emp => emp.salary * 0.5
},
report: {
Engineer: emp => `${emp.name}的技术报告`,
Manager: emp => `${emp.name}的管理报表`
}
};
// 执行函数
function execute(employees, operation) {
return employees.map(emp => {
const processor = visitors[operation][emp.type];
return processor ? processor(emp) : null;
});
}
//
