文章

设计模式解密:组合模式的终极指南(PHP/Go双实现)

一、什么是组合模式?

组合模式(Composite Pattern) 是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次关系。组合模式的核心思想是通过统一的接口处理单个对象和组合对象,使客户端可以一致地处理简单元素和复杂元素。

核心角色:

  1. 组件接口(Component):定义叶节点和组合节点的通用接口

  2. 叶节点(Leaf):表示树形结构中的叶子节点(无子节点)

  3. 组合节点(Composite):表示树形结构中的分支节点(有子节点)

组合模式结构图


二、适用场景

  • ✅ 需要表示“部分-整体”的层次结构

  • ✅ 需要统一处理简单元素和复杂元素

  • ✅ 需要动态添加或删除子节点

  • ✅ 需要递归遍历树形结构


三、PHP实现方案

1. 基础实现

interface Component {
    public function operation(): string;
}

class Leaf implements Component {
    private $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    public function operation(): string {
        return "Leaf: " . $this->name;
    }
}

class Composite implements Component {
    private $children = [];

    public function add(Component $component): void {
        $this->children[] = $component;
    }

    public function remove(Component $component): void {
        $this->children = array_filter($this->children, function ($child) use ($component) {
            return $child !== $component;
        });
    }

    public function operation(): string {
        $results = [];
        foreach ($this->children as $child) {
            $results[] = $child->operation();
        }
        return "Composite: [" . implode(", ", $results) . "]";
    }
}

// 使用示例
$leaf1 = new Leaf("Leaf 1");
$leaf2 = new Leaf("Leaf 2");
$leaf3 = new Leaf("Leaf 3");

$composite1 = new Composite();
$composite1->add($leaf1);
$composite1->add($leaf2);

$composite2 = new Composite();
$composite2->add($leaf3);
$composite2->add($composite1);

echo $composite2->operation(); // 输出:Composite: [Leaf: Leaf 3, Composite: [Leaf: Leaf 1, Leaf: Leaf 2]]

2. 进阶实现(带层级缩进)

interface Component {
    public function operation(int $level = 0): string;
}

class Leaf implements Component {
    private $name;

    public function __construct(string $name) {
        $this->name = $name;
    }

    public function operation(int $level = 0): string {
        return str_repeat("  ", $level) . "Leaf: " . $this->name . "\n";
    }
}

class Composite implements Component {
    private $children = [];

    public function add(Component $component): void {
        $this->children[] = $component;
    }

    public function remove(Component $component): void {
        $this->children = array_filter($this->children, function ($child) use ($component) {
            return $child !== $component;
        });
    }

    public function operation(int $level = 0): string {
        $results = [str_repeat("  ", $level) . "Composite:\n"];
        foreach ($this->children as $child) {
            $results[] = $child->operation($level + 1);
        }
        return implode("", $results);
    }
}

// 使用示例
$leaf1 = new Leaf("Leaf 1");
$leaf2 = new Leaf("Leaf 2");
$leaf3 = new Leaf("Leaf 3");

$composite1 = new Composite();
$composite1->add($leaf1);
$composite1->add($leaf2);

$composite2 = new Composite();
$composite2->add($leaf3);
$composite2->add($composite1);

echo $composite2->operation();
// 输出:
// Composite:
//   Leaf: Leaf 3
//   Composite:
//     Leaf: Leaf 1
//     Leaf: Leaf 2

四、Go实现方案

1. 基础实现

package main

import (
    "fmt"
    "strings"
)

type Component interface {
    Operation() string
}

type Leaf struct {
    name string
}

func NewLeaf(name string) *Leaf {
    return &Leaf{name: name}
}

func (l *Leaf) Operation() string {
    return "Leaf: " + l.name
}

type Composite struct {
    children []Component
}

func NewComposite() *Composite {
    return &Composite{children: []Component{}}
}

func (c *Composite) Add(component Component) {
    c.children = append(c.children, component)
}

func (c *Composite) Remove(component Component) {
    for i, child := range c.children {
        if child == component {
            c.children = append(c.children[:i], c.children[i+1:]...)
            break
        }
    }
}

func (c *Composite) Operation() string {
    results := []string{}
    for _, child := range c.children {
        results = append(results, child.Operation())
    }
    return "Composite: [" + strings.Join(results, ", ") + "]"
}

// 使用示例
func main() {
    leaf1 := NewLeaf("Leaf 1")
    leaf2 := NewLeaf("Leaf 2")
    leaf3 := NewLeaf("Leaf 3")

    composite1 := NewComposite()
    composite1.Add(leaf1)
    composite1.Add(leaf2)

    composite2 := NewComposite()
    composite2.Add(leaf3)
    composite2.Add(composite1)

    fmt.Println(composite2.Operation()) // 输出:Composite: [Leaf: Leaf 3, Composite: [Leaf: Leaf 1, Leaf: Leaf 2]]
}

2. 进阶实现(带层级缩进)

package main

import (
    "fmt"
    "strings"
)

type Component interface {
    Operation(level int) string
}

type Leaf struct {
    name string
}

func NewLeaf(name string) *Leaf {
    return &Leaf{name: name}
}

func (l *Leaf) Operation(level int) string {
    return strings.Repeat("  ", level) + "Leaf: " + l.name + "\n"
}

type Composite struct {
    children []Component
}

func NewComposite() *Composite {
    return &Composite{children: []Component{}}
}

func (c *Composite) Add(component Component) {
    c.children = append(c.children, component)
}

func (c *Composite) Remove(component Component) {
    for i, child := range c.children {
        if child == component {
            c.children = append(c.children[:i], c.children[i+1:]...)
            break
        }
    }
}

func (c *Composite) Operation(level int) string {
    results := []string{strings.Repeat("  ", level) + "Composite:\n"}
    for _, child := range c.children {
        results = append(results, child.Operation(level+1))
    }
    return strings.Join(results, "")
}

// 使用示例
func main() {
    leaf1 := NewLeaf("Leaf 1")
    leaf2 := NewLeaf("Leaf 2")
    leaf3 := NewLeaf("Leaf 3")

    composite1 := NewComposite()
    composite1.Add(leaf1)
    composite1.Add(leaf2)

    composite2 := NewComposite()
    composite2.Add(leaf3)
    composite2.Add(composite1)

    fmt.Println(composite2.Operation(0))
    // 输出:
    // Composite:
    //   Leaf: Leaf 3
    //   Composite:
    //     Leaf: Leaf 1
    //     Leaf: Leaf 2
}

五、关键实现差异对比

特性

PHP

Go

接口定义

使用interface关键字

使用interface关键字

组合方式

通过数组存储子节点

通过切片存储子节点

递归遍历

使用foreach循环

使用for循环

类型系统

强类型需显式声明

类型推断简化代码

层级缩进

使用str_repeat函数

使用strings.Repeat函数


六、模式优缺点分析

👍 优点:

  • 统一处理简单元素和复杂元素:客户端可以一致地处理单个对象和组合对象

  • 简化客户端代码:客户端无需关心对象的具体类型

  • 支持递归遍历:方便处理树形结构

👎 缺点:

  • 增加复杂度:引入额外的类和接口

  • 设计难度高:需要合理划分组件接口


七、实际应用案例

1. 文件系统

// PHP示例
$file1 = new File("file1.txt");
$file2 = new File("file2.txt");
$folder1 = new Folder("Folder 1");
$folder1->add($file1);
$folder1->add($file2);

$file3 = new File("file3.txt");
$folder2 = new Folder("Folder 2");
$folder2->add($file3);
$folder2->add($folder1);

echo $folder2->operation();

2. 组织架构

// Go示例
employee1 := NewEmployee("John Doe")
employee2 := NewEmployee("Jane Doe")
department1 := NewDepartment("Engineering")
department1.Add(employee1)
department1.Add(employee2)

employee3 := NewEmployee("Alice Smith")
department2 := NewDepartment("HR")
department2.Add(employee3)
department2.Add(department1)

fmt.Println(department2.Operation(0))

3. 菜单系统

// PHP示例
$item1 = new MenuItem("Home");
$item2 = new MenuItem("About");
$subMenu = new Menu("Services");
$subMenu->add(new MenuItem("Web Development"));
$subMenu->add(new MenuItem("Mobile Development"));

$menu = new Menu("Main Menu");
$menu->add($item1);
$menu->add($item2);
$menu->add($subMenu);

echo $menu->operation();

八、与装饰器模式的区别

组合模式

装饰器模式

目的

表示“部分-整体”的层次结构

动态扩展对象功能

核心思想

统一处理简单元素和复杂元素

包装对象

适用场景

树形结构

功能扩展

对象关系

父子关系

装饰者与目标接口有直接关系


九、总结

组合模式是处理树形结构的利器,通过统一的接口处理单个对象和组合对象,它提供了极大的灵活性和扩展性。无论是PHP的数组存储还是Go的切片存储,组合模式都能显著提升代码的可维护性和可扩展性。

在下一篇文章中,我们将探讨 装饰器模式 及其在动态扩展对象功能中的应用。敬请期待!


下一篇预告:设计模式系列(九)——装饰器模式:动态扩展的艺术

License:  CC BY 4.0