组合模式在Go中通过接口和结构体嵌套实现统一处理叶子与容器,核心是定义公共接口(如Component),由Leaf(如File)和Composite(如Folder)共同实现,支持递归操作整棵树。

组合模式(Composite Pattern)在 Go 语言中不依赖继承,而是通过接口和结构体嵌套来实现“统一处理叶子与容器”的能力。核心在于定义一个公共行为接口,让单个对象(Leaf)和组合对象(Composite)都实现它,从而客户端无需区分类型即可递归操作整棵树。
定义统一组件接口
先抽象出所有节点共有的行为,比如 Print()、Count() 或 Execute()。Go 中用接口表达最自然:
// Component 定义通用操作
type Component interface {
Print(indent string)
Count() int
}
// Leaf 实现简单行为
type File struct {
name string
}
func (f File) Print(indent string) {
fmt.Printf(#"%s├─ %s (file)\n", indent, f.name)
}
func (f File) Count() int { // 单个叶子计为1
return 1
}
用切片+嵌入实现组合容器
Composite 结构体持有子组件切片,并内嵌接口方法(或显式转发),体现“组合即代理”。Go 没有继承,但可通过字段嵌入 + 方法委托达成类似效果:
type Folder struct {
name string
childs []Component // 存放 File 或其他 Folder
}
func (f *Folder) Add(child Component) {
f.childs = append(f.childs, child)
}
func (f *Folder) Print(indent string) {
fmt.Printf(#"%s? %s (folder)\n", indent, f.name)
for i, c := range f.childs {
childIndent := indent + "│ "
if i == len(f.childs)-1 { childIndent = indent + "└ " }
c.Print(childIndent)
}
}
func (f *Folder) Count() int {
total := 1 // 自身算1个节点
for _, c := range f.childs {
total += c.Count()
}
return total
}
避免常见陷阱:nil 指针与值接收器
组合模式中容易踩坑的两个点:
立即学习“go语言免费学习笔记(深入)”;
-
Add 方法必须传指针:Folder 是结构体,若用值接收器,Add 修改的是副本,子节点不会真正加入;务必用
*Folder -
初始化 childs 切片:声明
childs []Component后需在 NewFolder 中初始化为make([]Component, 0),否则 append 会 panic - Leaf 不要意外实现 Composite 方法:如 File 不该有 Add,若误加会导致语义混乱;接口只暴露必要行为即可
优化方向:支持泛型与懒加载
Go 1.18+ 可用泛型提升复用性,例如封装通用树遍历:
func Traverse[T Component](root T, fn func(T)) {
fn(root)
if folder, ok := interface{}(root).(interface{ Childs() []Component }); ok {
for _, c := range folder.Childs() {
Traverse(c, fn)
}
}
}
// Folder 补充 Childs() 方法
func (f *Folder) Childs() []Component { return f.childs }
对超大目录树,还可将 childs 设为 func() []Component 类型,实现按需加载(lazy loading),减少内存占用。
基本上就这些。组合模式在 Go 里轻量又灵活,关键不在“像不像传统 OOP”,而在于是否让调用方代码更简洁、扩展更安全——把树形结构的操作收敛到接口里,新增节点类型只需实现几个方法,不用改遍历逻辑。










