22. 访问者模式(Visitor)
22. 访问者模式(Visitor)
问题:想在不修改对象结构的前提下,为其增加新操作。
核心:把操作从对象结构中分离出来,放到访问者中。
interface Visitor<R> {
visitNumber(node: NumberNode): R;
visitString(node: StringNode): R;
}
interface AstNode {
accept<R>(visitor: Visitor<R>): R;
}
class NumberNode implements AstNode {
constructor(public readonly value: number) {}
accept<R>(visitor: Visitor<R>): R { return visitor.visitNumber(this); }
}
class StringNode implements AstNode {
constructor(public readonly value: string) {}
accept<R>(visitor: Visitor<R>): R { return visitor.visitString(this); }
}
class TypeVisitor implements Visitor<string> {
visitNumber(node: NumberNode): string { return `数字: ${node.value}`; }
visitString(node: StringNode): string { return `字符串: "${node.value}"`; }
}
class LengthVisitor implements Visitor<number> {
visitNumber(node: NumberNode): number { return String(node.value).length; }
visitString(node: StringNode): number { return node.value.length; }
}
const nodes: AstNode[] = [new NumberNode(42), new StringNode('hello')];
nodes.forEach(n => console.log(n.accept(new TypeVisitor())));
// 数字: 42
// 字符串: "hello"
nodes.forEach(n => console.log(n.accept(new LengthVisitor())));
// 2
// 5package visitor
import (
"fmt"
"strconv"
)
type Visitor interface {
VisitNumber(n *NumberNode) string
VisitString(s *StringNode) string
}
type Node interface {
Accept(v Visitor) string
}
type NumberNode struct{ Value int }
func (n *NumberNode) Accept(v Visitor) string { return v.VisitNumber(n) }
type StringNode struct{ Value string }
func (s *StringNode) Accept(v Visitor) string { return v.VisitString(s) }
type TypeVisitor struct{}
func (t TypeVisitor) VisitNumber(n *NumberNode) string {
return fmt.Sprintf("数字: %d", n.Value)
}
func (t TypeVisitor) VisitString(s *StringNode) string {
return fmt.Sprintf("字符串: \"%s\"", s.Value)
}
type LengthVisitor struct{}
func (l LengthVisitor) VisitNumber(n *NumberNode) string {
return strconv.Itoa(len(strconv.Itoa(n.Value)))
}
func (l LengthVisitor) VisitString(s *StringNode) string {
return strconv.Itoa(len(s.Value))
}实战场景
- AST 分析:语法树节点稳定,但要不断增加类型检查、格式化、代码生成、lint 规则等操作。
- 文档导出:Markdown 或富文本节点结构不变,可以新增 HTML、PDF、纯文本、摘要等导出 visitor。
- 表单 schema 处理:同一套字段结构需要渲染、校验、生成默认值、生成文档、统计字段数量。
- 图形对象统计:图形类型稳定,但可以增加面积计算、碰撞检测、导出 SVG、生成缩略图等操作。
- 权限审计:资源树结构稳定,新增审计 visitor 遍历每个资源并收集风险点。
- 搜索索引构建:遍历内容树,为不同节点提取标题、正文、标签、附件文本。
- 序列化:对象结构稳定时,为 JSON、XML、二进制协议增加不同序列化 visitor。
- 规则引擎解释:表达式节点不变,但可以增加求值、打印、优化、依赖收集等操作。
- UI 可访问性检查:遍历组件树,检查缺失 label、颜色对比度、键盘交互等问题。
- 编译优化:编译器对 AST 增加常量折叠、死代码删除、作用域分析等 visitor。