本文目录导读:

我来详细讲解PHP中的组合模式处理树形结构。
什么是组合模式
组合模式是一种结构型设计模式,它允许将对象组合成树形结构来表示"部分-整体"的层次关系,客户端可以统一地处理单个对象和组合对象。
核心组件
- Component(组件):抽象接口,定义通用方法
- Leaf(叶子节点):没有子节点的对象
- Composite(组合节点):有子节点的对象,实现子节点管理
完整实现示例
基础组合模式
<?php
// 组件接口
interface Component
{
public function getName(): string;
public function getSize(): int;
public function display(int $indent = 0): string;
public function add(Component $component): void;
public function remove(Component $component): void;
public function getChild(int $index): ?Component;
}
// 叶子节点(文件)
class File implements Component
{
private string $name;
private int $size;
public function __construct(string $name, int $size)
{
$this->name = $name;
$this->size = $size;
}
public function getName(): string
{
return $this->name;
}
public function getSize(): int
{
return $this->size;
}
public function display(int $indent = 0): string
{
return str_repeat(" ", $indent) . "📄 {$this->name} ({$this->size} bytes)\n";
}
// 叶子节点没有子节点,这些方法可以空实现或抛异常
public function add(Component $component): void
{
throw new Exception("Cannot add to a file");
}
public function remove(Component $component): void
{
throw new Exception("Cannot remove from a file");
}
public function getChild(int $index): ?Component
{
return null;
}
}
// 组合节点(文件夹)
class Directory implements Component
{
private string $name;
private array $children = [];
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
public function getSize(): int
{
$totalSize = 0;
foreach ($this->children as $child) {
$totalSize += $child->getSize();
}
return $totalSize;
}
public function display(int $indent = 0): string
{
$output = str_repeat(" ", $indent) . "📁 {$this->name}/\n";
foreach ($this->children as $child) {
$output .= $child->display($indent + 1);
}
return $output;
}
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 getChild(int $index): ?Component
{
return $this->children[$index] ?? null;
}
public function getChildren(): array
{
return $this->children;
}
}
// 使用示例
function testFileSystem()
{
$root = new Directory("root");
$images = new Directory("images");
$images->add(new File("logo.png", 5000));
$images->add(new File("banner.jpg", 10000));
$docs = new Directory("docs");
$docs->add(new File("readme.txt", 2000));
$project = new Directory("project");
$project->add($images);
$project->add($docs);
$project->add(new File("main.php", 8000));
$root->add($project);
echo "文件系统结构:\n";
echo $root->display();
echo "\n总大小: " . $root->getSize() . " bytes\n";
}
testFileSystem();
增强版:组织架构示例
<?php
// 员工接口
interface EmployeeInterface
{
public function getName(): string;
public function getPosition(): string;
public function getSalary(): float;
public function getSubordinatesCount(): int;
public function display(int $indent = 0): string;
public function add(EmployeeInterface $employee): void;
public function remove(EmployeeInterface $employee): void;
public function getTotalSalary(): float;
}
// 员工(叶子节点)
class Employee implements EmployeeInterface
{
private string $name;
private string $position;
private float $salary;
public function __construct(string $name, string $position, float $salary)
{
$this->name = $name;
$this->position = $position;
$this->salary = $salary;
}
public function getName(): string
{
return $this->name;
}
public function getPosition(): string
{
return $this->position;
}
public function getSalary(): float
{
return $this->salary;
}
public function getSubordinatesCount(): int
{
return 0;
}
public function display(int $indent = 0): string
{
return str_repeat(" ", $indent) . "👤 {$this->name} - {$this->position} (¥{$this->salary})\n";
}
public function add(EmployeeInterface $employee): void
{
throw new Exception("Cannot add subordinates to an employee");
}
public function remove(EmployeeInterface $employee): void
{
throw new Exception("Cannot remove subordinates from an employee");
}
public function getTotalSalary(): float
{
return $this->salary;
}
}
// 部门经理(组合节点)
class Manager implements EmployeeInterface
{
private string $name;
private string $position;
private float $salary;
private array $subordinates = [];
public function __construct(string $name, string $position, float $salary)
{
$this->name = $name;
$this->position = $position;
$this->salary = $salary;
}
public function getName(): string
{
return $this->name;
}
public function getPosition(): string
{
return $this->position;
}
public function getSalary(): float
{
return $this->salary;
}
public function getSubordinatesCount(): int
{
$count = count($this->subordinates);
foreach ($this->subordinates as $subordinate) {
$count += $subordinate->getSubordinatesCount();
}
return $count;
}
public function display(int $indent = 0): string
{
$output = str_repeat(" ", $indent) . "👔 {$this->name} - {$this->position} (¥{$this->salary})\n";
foreach ($this->subordinates as $subordinate) {
$output .= $subordinate->display($indent + 1);
}
return $output;
}
public function add(EmployeeInterface $employee): void
{
$this->subordinates[] = $employee;
}
public function remove(EmployeeInterface $employee): void
{
$this->subordinates = array_filter($this->subordinates, function ($sub) use ($employee) {
return $sub !== $employee;
});
}
public function getTotalSalary(): float
{
$total = $this->salary;
foreach ($this->subordinates as $subordinate) {
$total += $subordinate->getTotalSalary();
}
return $total;
}
}
// 使用示例
function testOrganizationStructure()
{
// 创建组织结构
$ceo = new Manager("张三", "CEO", 50000);
$cto = new Manager("李四", "CTO", 35000);
$cfo = new Manager("王五", "CFO", 30000);
// 技术团队
$devLead1 = new Manager("赵六", "开发组长", 25000);
$devLead2 = new Manager("钱七", "开发组长", 25000);
$cto->add($devLead1);
$cto->add($devLead2);
$devLead1->add(new Employee("孙八", "高级开发", 20000));
$devLead1->add(new Employee("周九", "中级开发", 15000));
$devLead2->add(new Employee("吴十", "测试工程师", 18000));
$devLead2->add(new Employee("郑十一", "运维工程师", 22000));
// 财务团队
$cfo->add(new Employee("冯十二", "会计", 12000));
$cfo->add(new Employee("陈十三", "出纳", 8000));
$ceo->add($cto);
$ceo->add($cfo);
// 展示组织结构
echo "公司组织结构:\n";
echo $ceo->display();
echo "\n公司总人力成本: ¥" . $ceo->getTotalSalary() . "\n";
echo "公司总人数: " . $ceo->getSubordinatesCount() . "\n";
}
testOrganizationStructure();
高级功能:树形数据遍历
<?php
// 树形数据遍历迭代器
class TreeIterator implements Iterator
{
private array $nodes = [];
private int $position = 0;
public function __construct(Component $root)
{
$this->flatten($root);
}
private function flatten(Component $node): void
{
$this->nodes[] = $node;
// 如果是组合节点,遍历子节点
if ($node instanceof Directory) {
foreach ($node->getChildren() as $child) {
$this->flatten($child);
}
}
}
public function current(): Component
{
return $this->nodes[$this->position];
}
public function key(): int
{
return $this->position;
}
public function next(): void
{
$this->position++;
}
public function rewind(): void
{
$this->position = 0;
}
public function valid(): bool
{
return isset($this->nodes[$this->position]);
}
public function count(): int
{
return count($this->nodes);
}
}
// 访问者模式示例(可选增强)
interface Visitor
{
public function visitFile(File $file): void;
public function visitDirectory(Directory $directory): void;
}
class FileSizeVisitor implements Visitor
{
private int $totalSize = 0;
private int $fileCount = 0;
private int $dirCount = 0;
public function visitFile(File $file): void
{
$this->totalSize += $file->getSize();
$this->fileCount++;
}
public function visitDirectory(Directory $directory): void
{
$this->dirCount++;
foreach ($directory->getChildren() as $child) {
if ($child instanceof File) {
$this->visitFile($child);
} elseif ($child instanceof Directory) {
$this->visitDirectory($child);
}
}
}
public function getStats(): array
{
return [
'totalSize' => $this->totalSize,
'fileCount' => $this->fileCount,
'dirCount' => $this->dirCount
];
}
}
// 高级使用示例
function testAdvancedFeatures()
{
// 构建文件系统
$root = new Directory("root");
$src = new Directory("src");
$src->add(new File("app.php", 15000));
$src->add(new File("config.php", 3000));
$public = new Directory("public");
$public->add(new File("index.html", 5000));
$public->add(new File("style.css", 2000));
$root->add($src);
$root->add($public);
$root->add(new File("README.md", 1000));
// 使用迭代器遍历
echo "使用迭代器遍历所有节点:\n";
$iterator = new TreeIterator($root);
foreach ($iterator as $index => $node) {
echo " [{$index}] {$node->getName()}\n";
}
echo "总节点数: " . count($iterator) . "\n\n";
// 使用访问者统计
$visitor = new FileSizeVisitor();
$visitor->visitDirectory($root);
$stats = $visitor->getStats();
echo "文件系统统计:\n";
echo " 文件数量: {$stats['fileCount']}\n";
echo " 文件夹数量: {$stats['dirCount']}\n";
echo " 总大小: {$stats['totalSize']} bytes\n";
}
testAdvancedFeatures();
实际应用场景
菜单系统
class MenuItem extends Component
{
// 菜单项可以是叶子或组合
}
class Menu extends Composite
{
// 菜单包含子菜单和菜单项
}
权限树
class PermissionNode
{
// 权限模块可以有子权限
}
分类系统
class Category
{
// 商品分类支持无限层级
}
最佳实践
- 统一接口:叶子节点和组合节点实现相同接口
- 安全操作:叶子节点对添加/移除操作抛出异常或静默处理
- 递归操作:使用递归处理树形结构
- 缓存机制:对于频繁访问的操作可以考虑缓存结果
- 性能优化:大型树形结构考虑使用生成器或延迟加载
优缺点
优点:
- 清晰地表示部分-整体层级关系
- 客户端可以一致地处理简单和复杂元素
- 易于增加新类型的组件
缺点:
- 可能会使设计过于复杂
- 对类型系统的约束较弱
组合模式非常适合处理树形结构,如文件系统、组织架构、菜单系统等场景。