TypeScript设计模式:访问者模式

33 阅读4分钟

访问者模式(Visitor Pattern)是一种行为设计模式,用于将操作逻辑与对象结构分离,允许在不修改对象结构的情况下为对象添加新操作。

设计模式原理

访问者模式通过定义访问者接口,将操作逻辑(如统计、校验)从节点结构中分离。节点通过 accept 方法接受访问者,调用其对应操作。在工作流引擎中,结合泛型节点 GenericNode<T>,访问者模式可统一处理不同类型的节点(如触发器、动作、条件),通过泛型确保参数类型安全,同时支持动态添加新操作。

访问者模式的结构

  1. Visitor(访问者接口) :声明通用的访问方法,结合泛型支持不同节点类型。
  2. ConcreteVisitor(具体访问者) :实现访问者接口,提供具体操作逻辑。
  3. Element(元素接口) :定义接受访问者的方法,如 accept
  4. ConcreteElement(具体元素) :使用泛型节点 GenericNode<T> 统一实现,处理不同参数类型。
  5. 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 配置定义工作流,访问者动态为节点添加统计、校验等操作,无需修改节点类。泛型确保了类型安全,减少了代码冗余,提升了系统的分离性、扩展性和一致性,适合动态、复杂的工作流系统。