接口是组合模式的骨架,确保叶子和容器行为统一;Component只声明GetSize()等共有方法,不存状态;children应为[]Component类型;Leaf不实现Add/Remove,推荐安全模式;递归调用由接口方法自然展开。

接口是组合模式的骨架,不是装饰或约束,而是让叶子和容器真正“看起来一样”的关键。没有统一接口,树形结构就失去了透明性,客户端必须不断判断类型、切换逻辑,树也就不再是树,而是一堆需要手动拼接的散点。
接口要只声明行为,不掺杂数据或实现细节
Component 接口只需定义所有节点共有的方法,比如 GetSize()、Display() 或 Execute()。它不保存名字、路径、子项列表这些状态——那是 Leaf 和 Composite 各自该管的事。Go 里尤其要注意:接口不能有字段,也不该试图用嵌入空结构体来“模拟基类”。只要方法签名一致,结构体就自动满足接口,这是隐式契约,不是继承关系。
Composite 的 children 切片必须存接口类型,不是具体类型指针
常见错误是写成 []*Component,但 Component 是接口名,不能取地址。正确做法是:children []Component。这样既能存 *Leaf,也能存 *Composite,因为两者都实现了 Component 接口。Add 方法接收 Component 参数,正是为了支持这种混合嵌套——加一个文件,加一个子目录,甚至加一个压缩包(它自己也是 Composite),对父节点来说毫无区别。
Leaf 不该实现 Add/Remove 这类容器专属方法
如果接口里强行塞进 Add(Component),Leaf 就得应付它:要么空实现,要么 panic,容易误导调用方。这就是“透明模式”和“安全模式”的取舍。推荐安全模式——接口只保留所有节点真正在用的方法。需要管理子项的操作,只在 Composite 类型上暴露。客户端若想添加子项,自然会拿到 *Composite 实例,不会误对 *Leaf 调用 Add。清晰比“看起来统一”更重要。
递归调用靠接口方法自然展开,无需客户端操心
- 当调用 root.GetSize(),Composite 的实现会遍历 children,对每个 child 调用 GetSize()
- child 可能是 *Leaf(直接返回自身大小),也可能是另一个 *Composite(继续递归)
- 客户端完全不用知道哪一层是叶子、哪一层是容器,就像遍历一棵真正的树
不复杂但容易忽略:接口不是为了“看起来像”,而是为了让调用逻辑能自然下沉。树的深度、节点类型、嵌套方式,全由接口方法的递归实现承载,而不是靠类型断言或 if-else 分支撑着。

















