组合模式的核心是将对象组合成树形结构以表示部分-整体的层次结构,让客户端可以一致地对待单个对象和组合对象,在Golang中可以利用接口和多态特性实现这一模式,适配树形结构的管理需求。

组合模式核心角色
实现组合模式前需要先明确三个核心角色:
- 组件接口:定义叶子节点和组合节点的公共操作方法,是统一处理的基础
- 叶子节点:树形结构中的最底层节点,没有子节点,实现组件接口的方法
- 组合节点:包含子节点的节点,同样实现组件接口,同时需要维护子节点集合,提供添加、删除子节点的方法
Golang实现步骤
1. 定义组件接口
首先定义统一的组件接口,包含树形结构中节点需要支持的操作,比如获取名称、展示结构、添加子节点等:
// Component 组件接口,定义所有节点的公共方法
type Component interface {
// GetName 获取节点名称
GetName() string
// Show 展示节点结构,level表示当前节点的层级
Show(level int)
// Add 添加子节点,组合节点需要实现该方法,叶子节点可以不实现或返回错误
Add(c Component) error
// Remove 移除子节点,组合节点需要实现该方法,叶子节点可以不实现或返回错误
Remove(c Component) error
}2. 实现叶子节点
叶子节点是最底层的节点,没有子节点,实现组件接口时,添加和移除子节点的方法可以返回不支持的错误:
// Leaf 叶子节点结构体
type Leaf struct {
name string
}
// NewLeaf 创建叶子节点实例
func NewLeaf(name string) *Leaf {
return &Leaf{name: name}
}
// GetName 实现Component接口的GetName方法
func (l *Leaf) GetName() string {
return l.name
}
// Show 实现Component接口的Show方法,展示当前节点
func (l *Leaf) Show(level int) {
// 根据层级添加缩进,方便查看树形结构
for i := 0; i < level; i++ {
print(" ")
}
println("叶子节点:", l.name)
}
// Add 叶子节点不支持添加子节点,返回错误
func (l *Leaf) Add(c Component) error {
return fmt.Errorf("叶子节点%s不支持添加子节点", l.name)
}
// Remove 叶子节点不支持移除子节点,返回错误
func (l *Leaf) Remove(c Component) error {
return fmt.Errorf("叶子节点%s不支持移除子节点", l.name)
}3. 实现组合节点
组合节点需要维护子节点集合,实现组件接口的所有方法,支持添加、移除子节点,遍历展示所有子节点:
// Composite 组合节点结构体
type Composite struct {
name string
children []Component
}
// NewComposite 创建组合节点实例
func NewComposite(name string) *Composite {
return &Composite{
name: name,
children: make([]Component, 0),
}
}
// GetName 实现Component接口的GetName方法
func (c *Composite) GetName() string {
return c.name
}
// Show 实现Component接口的Show方法,先展示自身,再遍历展示所有子节点
func (c *Composite) Show(level int) {
for i := 0; i < level; i++ {
print(" ")
}
println("组合节点:", c.name)
// 递归展示所有子节点,层级加1
for _, child := range c.children {
child.Show(level + 1)
}
}
// Add 实现Component接口的Add方法,添加子节点
func (c *Composite) Add(child Component) error {
c.children = append(c.children, child)
return nil
}
// Remove 实现Component接口的Remove方法,移除指定子节点
func (c *Composite) Remove(child Component) error {
for i, ch := range c.children {
if ch.GetName() == child.GetName() {
c.children = append(c.children[:i], c.children[i+1:]...)
return nil
}
}
return fmt.Errorf("子节点%s不存在", child.GetName())
}完整使用示例
下面构建一个简单的公司组织架构树,根节点是公司,下面有技术部、人事部两个组合节点,技术部下有开发组、测试组两个组合节点,每个组下有具体的员工叶子节点:
package main
import "fmt"
// 此处粘贴上面定义的Component接口、Leaf结构体、Composite结构体的代码
func main() {
// 创建根节点:公司
root := NewComposite("XX科技公司")
// 创建一级部门节点
techDept := NewComposite("技术部")
hrDept := NewComposite("人事部")
// 技术部下的子部门
devGroup := NewComposite("开发组")
testGroup := NewComposite("测试组")
// 添加员工叶子节点
devGroup.Add(NewLeaf("张三"))
devGroup.Add(NewLeaf("李四"))
testGroup.Add(NewLeaf("王五"))
// 组装树形结构
techDept.Add(devGroup)
techDept.Add(testGroup)
root.Add(techDept)
root.Add(hrDept)
// 展示整个组织架构
println("公司组织架构:")
root.Show(0)
// 尝试从开发组移除员工
err := devGroup.Remove(NewLeaf("李四"))
if err != nil {
fmt.Println("移除失败:", err)
} else {
println("移除李四后的开发组结构:")
devGroup.Show(1)
}
}注意事项
在实际使用Golang实现组合模式时,需要注意几个问题:
- 如果叶子节点不需要支持添加、移除子节点,可以在组件接口中只定义公共的展示、获取名称方法,添加移除方法单独放在组合节点的接口中,避免叶子节点实现无用方法
- 组合节点的子节点集合可以使用切片或者链表存储,根据实际的遍历、增删频率选择合适的结构
- 遍历树形结构时如果需要支持不同的遍历方式,可以在组件接口中增加遍历方法,或者在外部实现遍历逻辑
- 如果节点需要支持更多操作,比如修改名称、统计节点数量,只需要在组件接口中增加对应方法,所有节点实现即可