访问者模式(Visitor Pattern)是一种行为设计模式,用于将操作逻辑与对象结构分离,允许在不修改对象结构的情况下为对象添加新操作。
设计模式原理
访问者模式通过定义访问者接口,将操作逻辑(如统计、校验)从节点结构中分离。节点通过 accept 方法接受访问者,调用其对应操作。在工作流引擎中,结合泛型节点 GenericNode<T>,访问者模式可统一处理不同类型的节点(如触发器、动作、条件),通过泛型确保参数类型安全,同时支持动态添加新操作。
访问者模式的结构
- Visitor(访问者接口) :声明通用的访问方法,结合泛型支持不同节点类型。
- ConcreteVisitor(具体访问者) :实现访问者接口,提供具体操作逻辑。
- Element(元素接口) :定义接受访问者的方法,如
accept。 - ConcreteElement(具体元素) :使用泛型节点
GenericNode<T>统一实现,处理不同参数类型。 - Client(客户端) :通过 JSON 配置创建节点并应用访问者。
优点
- 分离性:操作逻辑与节点结构解耦,节点类专注于核心功能。
- 扩展性:新增操作只需实现新访问者,无需修改节点类。
- 类型安全:泛型确保节点参数与访问者逻辑匹配。
- 一致性:统一接口处理所有节点类型。
适用场景
- 需要为工作流节点添加多种操作(如统计、校验、序列化)。
- 节点结构稳定,但操作频繁变化。
- 希望通过泛型减少重复代码,提高类型安全。
TypeScript 实现示例
以下示例在工作流引擎中使用泛型节点 GenericNode<T>,通过访问者模式为节点添加统计和校验操作。客户端通过 JSON 配置定义工作流,引擎使用统一的 accept 方法调用访问者逻辑。
// 节点类型枚举
enum NodeType {
TRIGGER = 'trigger',
ACTION = 'action',
CONDITION = 'condition'
}
// JSON 节点配置接口
interface NodeConfigJson<T = any> {
type: NodeType;
params: T & { id: string };
}
// 节点参数类型
interface TriggerParams { id: string; event: string }
interface ActionParams { id: string; operation: string }
interface ConditionParams { id: string; condition: string }
// 泛型节点接口
interface WorkflowNode<T> {
type: NodeType;
params: T;
accept<R>(visitor: WorkflowNodeVisitor<T, R>): R;
execute(): string;
getDetails(): string;
}
// 访问者接口(泛型)
interface WorkflowNodeVisitor<T, R> {
visit(node: WorkflowNode<T>): R;
}
// 泛型节点实现
class GenericNode<T> implements WorkflowNode<T> {
constructor(public type: NodeType, public params: T) {}
accept<R>(visitor: WorkflowNodeVisitor<T, R>): R {
return visitor.visit(this);
}
execute(): string {
switch (this.type) {
case NodeType.TRIGGER:
const triggerParams = this.params as TriggerParams;
return `触发器节点(ID: ${triggerParams.id}):触发事件 ${triggerParams.event || '未知事件'}`;
case NodeType.ACTION:
const actionParams = this.params as ActionParams;
return `动作节点(ID: ${actionParams.id}):执行操作 ${actionParams.operation || '未知操作'}`;
case NodeType.CONDITION:
const conditionParams = this.params as ConditionParams;
return `条件节点(ID: ${conditionParams.id}):评估条件 ${conditionParams.condition || '未知条件'}`;
default:
return `未知节点类型:${this.type}`;
}
}
getDetails(): string {
return `详情:${this.type} 节点,ID=${this.params.id}, 参数=${JSON.stringify(this.params)}`;
}
}
// 具体访问者:统计节点信息
class StatsVisitor implements WorkflowNodeVisitor<any, void> {
private nodeCount: { [key in NodeType]: number } = {
[NodeType.TRIGGER]: 0,
[NodeType.ACTION]: 0,
[NodeType.CONDITION]: 0
};
visit(node: WorkflowNode<any>): void {
this.nodeCount[node.type]++;
}
getStats(): string {
return `统计:触发器=${this.nodeCount[NodeType.TRIGGER]}, 动作=${this.nodeCount[NodeType.ACTION]}, 条件=${this.nodeCount[NodeType.CONDITION]}`;
}
}
// 具体访问者:校验节点参数
class ValidationVisitor implements
WorkflowNodeVisitor<TriggerParams, string[]>,
WorkflowNodeVisitor<ActionParams, string[]>,
WorkflowNodeVisitor<ConditionParams, string[]> {
visit(node: WorkflowNode<any>): string[] {
switch (node.type) {
case NodeType.TRIGGER:
return node.params.event ? [] : [`触发器节点(ID: ${node.params.id})缺少事件参数`];
case NodeType.ACTION:
return node.params.operation ? [] : [`动作节点(ID: ${node.params.id})缺少操作参数`];
case NodeType.CONDITION:
return node.params.condition ? [] : [`条件节点(ID: ${node.params.id})缺少条件参数`];
default:
return [`未知节点类型:${node.type}`];
}
}
}
// 工厂类:创建节点
class WorkflowNodeFactory {
createNode<T>(config: NodeConfigJson<T>): WorkflowNode<T> {
return new GenericNode<T>(config.type, config.params);
}
}
// 客户端代码:处理工作流并应用访问者
function processWorkflow(factory: WorkflowNodeFactory, nodes: NodeConfigJson[]) {
const workflowNodes = nodes.map(config => factory.createNode(config));
// 执行节点
console.log("=== 节点执行 ===");
workflowNodes.forEach((node, index) => {
console.log(`节点 ${index + 1} 执行:${node.execute()}`);
console.log(`节点 ${index + 1} 详情:${node.getDetails()}`);
console.log("");
});
// 应用统计访问者
const statsVisitor = new StatsVisitor();
console.log("=== 统计访问 ===");
workflowNodes.forEach(node => node.accept(statsVisitor));
console.log(statsVisitor.getStats());
// 应用校验访问者
const validationVisitor = new ValidationVisitor();
console.log("\n=== 校验访问 ===");
const errors = workflowNodes.flatMap(node => node.accept(validationVisitor));
if (errors.length === 0) {
console.log("所有节点参数校验通过");
} else {
errors.forEach(error => console.log(`校验错误:${error}`));
}
}
// 测试代码
function main() {
// JSON 配置:用户登录 → 检查新用户 → 发送欢迎邮件
const workflowNodes: NodeConfigJson[] = [
{
type: NodeType.TRIGGER,
params: { id: "TRG001", event: "用户登录" }
},
{
type: NodeType.CONDITION,
params: { id: "COND001", condition: "是否新用户" }
},
{
type: NodeType.ACTION,
params: { id: "ACT001", operation: "发送欢迎邮件" }
},
{
type: NodeType.ACTION,
params: { id: "ACT002" } // 缺少 operation 参数
}
];
console.log("工作流:用户登录 → 检查新用户 → 发送欢迎邮件");
const factory = new WorkflowNodeFactory();
processWorkflow(factory, workflowNodes);
}
main();
运行结果
工作流:用户登录 → 检查新用户 → 发送欢迎邮件
=== 节点执行 ===
节点 1 执行:触发器节点(ID: TRG001):触发事件 用户登录
节点 1 详情:详情:trigger 节点,ID=TRG001, 参数={"id":"TRG001","event":"用户登录"}
节点 2 执行:条件节点(ID: COND001):评估条件 是否新用户
节点 2 详情:详情:condition 节点,ID=COND001, 参数={"id":"COND001","condition":"是否新用户"}
节点 3 执行:动作节点(ID: ACT001):执行操作 发送欢迎邮件
节点 3 详情:详情:action 节点,ID=ACT001, 参数={"id":"ACT001","operation":"发送欢迎邮件"}
节点 4 执行:动作节点(ID: ACT002):执行操作 未知操作
节点 4 详情:详情:action 节点,ID=ACT002, 参数={"id":"ACT002"}
=== 统计访问 ===
统计:触发器=1, 动作=2, 条件=1
=== 校验访问 ===
校验错误:动作节点(ID: ACT002)缺少操作参数
总结
通过泛型节点 GenericNode<T> 和统一的访问接口,访问者模式在工作流引擎中实现了操作逻辑与节点结构的解耦。客户端通过 JSON 配置定义工作流,访问者动态为节点添加统计、校验等操作,无需修改节点类。泛型确保了类型安全,减少了代码冗余,提升了系统的分离性、扩展性和一致性,适合动态、复杂的工作流系统。