新增操作时不能改Element接口,因其违反开闭原则;应新增Visitor实现类(如MarkdownVisitor),通过VisitFile/VisitDirectory方法封装逻辑,保持Element结构稳定。
新增操作时为什么不能改 Element 接口
访问者模式的核心契约是:
接口只定义
方法,所有具体元素(如
、
)都必须实现它。一旦你为某个具体元素类型新增一个操作(比如「计算校验和」),却去修改
或
的结构(例如加新方法),就破坏了开闭原则——后续每加一个操作都要动已有类,可维护性立刻崩塌。
真正该做的,是新增一个实现了
接口的新类型,比如
,然后在它的
和
方法里写逻辑。
Visitor 接口如何设计才支持无侵入扩展
关键在于:Visitor 接口的方法签名必须覆盖全部 Element 类型,且方法名要能体现“被访问对象”的语义,而不是操作意图。否则每次加新 Element 类型(比如加个
),就得改 Visitor 接口,又违反开闭。
常见错误:
立即学习
“
go语言免费学习笔记(深入)
”;
把
设计成泛型接口(如
),导致每个新操作都要声明新接口,无法统一调度
用反射动态调用 Visit 方法——失去编译期检查,运行时报错难定位
在
里硬
编码
switch
type,绕过 Visitor 接口,等于没用模式
实际新增一个操作:生成 Markdown 目录树
假设已有
和
,现在要支持「输出为 Markdown 格式目录结构」。不改任何现有 Element 实现,只加:
React Native For Android 源码编译 中文WORD版
本文档主要讲述的是React Native For Android 源码编译;希望对大家会有帮助;感兴趣的朋友可以过来看看
下载
调用方式干净利落:
这个过程没碰
、
、
接口一行代码。
容易被忽略的边界:循环引用与访问深度控制
真实文件系统可能有符号链接甚至硬链接环,
递归调用时若不做防环处理,会栈溢出。这不是 Visitor 模式本身的问题,但扩展新 Visitor 时极易忽略。
建议在 Visitor 实现里自带访问路径记录(比如
记住已访问对象地址),或传入上下文控制最大深度:
在
开头加
避免在
方法里直接递归调用自身,而是由 Visitor 主动决定是否深入子节点
如果 Element 结构本身支持 ID(如 i
node
),Visitor 可以用
去重
没有统一的「安全访问器基类」,每个新 Visitor 都得自己考虑这点——这是扩展性代价中最容易漏掉的一块。
ElementAccept(visitor Visitor)FileDirectoryFileDirectoryVisitorChecksumVisitorVisitFileVisitDirectorySymlinktype Visitor interface {
VisitFile(*File)
VisitDirectory(*Directory)
// 不要叫 VisitForBackup() 或 VisitForLog() —— 这是操作意图,会随业务变
// 也不要漏掉未来可能的类型,宁可预留 VisitSymlink(*Symlink) 并空实现
}VisitorVisitor[T any]AcceptFileDirectorytype MarkdownVisitor struct {
buf strings.Builder
indent int
}
func (v MarkdownVisitor) VisitFile(f File) {
v.buf.WriteString(strings.Repeat(" ", v.indent))
v.buf.WriteString("- ? ")
v.buf.WriteString(f.Name)
v.buf.WriteString("\n")
}
func (v MarkdownVisitor) VisitDirectory(d Directory) {
v.buf.WriteString(strings.Repeat(" ", v.indent))
v.buf.WriteString("- ? ")
v.buf.WriteString(d.Name)
v.buf.WriteString("\n")
v.indent++
for _, child := range d.Children {
child.Accept(v) // 注意:这里依赖每个 child 都实现了 Accept
}
v.indent--
}
func (v *MarkdownVisitor) Result() string {
return v.buf.String()
}
visitor := &MarkdownVisitor{}
root.Accept(visitor)
fmt.Println(visitor.Result())FileDirectoryVisitorAcceptmap[uintptr]boolVisitDirectoryif depth > maxDepth { return }Acceptmap[uint64]bool