go中组合模式需用接口定义component行为,容器节点嵌入切片存子节点,避免继承;注意递归安全、路径归一化、并发锁和封装性。
组合模式在 Go 里没有“抽象基类”,得靠接口和嵌入
Go 没有传统面向对象的继承体系,所以无法像 Java/C# 那样定义 Component 抽象类再让 Leaf 和 Composite 去继承。实际做法是定义一个统一行为接口(比如 Component),所有叶子节点和容器节点都实现它;容器节点内部用切片存其他 Component,并通过结构体字段嵌入来复用通用逻辑。
常见错误是试图用空结构体或泛型约束模拟“父类”,反而让接口边界模糊、类型断言变多。正确姿势是:接口只声明行为(如 Execute()、Count()),具体结构体各自实现,容器节点通过字段持有子节点切片,不强求“父子同构”。
-
Component接口应尽量窄,只包含组合树遍历时真正需要的公共方法 - 容器结构体中子节点字段建议命名为
children,类型为[]Component,不要用interface{}或泛型切片 - 避免在
Composite中暴露children字段为导出字段——外部直接修改会破坏封装性
递归遍历必须小心栈溢出和循环引用
组合模式天然依赖递归,但 Go 默认 goroutine 栈大小有限(通常 2KB),深层嵌套(比如上万层的树)会触发 runtime: goroutine stack exceeds 1000000000-byte limit。更隐蔽的问题是循环引用:A 的子节点含 B,B 的子节点又含 A,遍历时无限递归。
解决方式不是简单加深度限制,而是引入访问标记或路径追踪。生产环境建议用 map[uintptr]bool 记录已访问节点地址,或用 map[*Composite]bool(需确保指针稳定)。若节点可比较且无并发修改,也可用节点 ID 做键。