19.2组合模式

组合模式(Composite),将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
组合模式(Composite)结构图 第十九章 分公司=一部门——组合模式 - 图1代码示例
Component 为组合中的对象声明接口,在适当情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理 Component 的子部件。

  1. abstract class Component
  2. {
  3. protected $name = '';
  4. public function __construct($name)
  5. {
  6. $this->name = $name;
  7. }
  8. //通常都用 Add 和 Remove 方法来提供
  9. //增加或移除树叶或树枝的功能
  10. abstract function Add($c);
  11. abstract function Remove($c);
  12. abstract function Display($depth);
  13. }

Leaf在组合中表示叶节点对象,叶节点没有子节点

  1. class Leaf extends Component
  2. {
  3. public function __construct($name)
  4. {
  5. parent::__construct($name);
  6. }
  7. //由于叶子没有再增加分枝和树叶,所以 Add 和
  8. //Remove 方法实现它没有意义,但这样做可以消除
  9. //叶节点和枝节点对象在抽象层次的区别,它们具备
  10. //完全一致的接口。
  11. public function Add($c)
  12. {
  13. echo 'Cannot add to a leaf' . PHP_EOL;
  14. }
  15. public function Remove($c)
  16. {
  17. echo 'Cannot remove from a leaf' . PHP_EOL;
  18. }
  19. //叶节点的具体方法,此处是显示其名称和级别
  20. public function Display($depth)
  21. {
  22. echo Str::String('-', $depth) . $this->name . PHP_EOL;
  23. }
  24. }

Composite 定义有枝节点行为,用来存储子部件,在 Component 接口中实现与子部件有关的操作,比如增加 Add 和删除 Remove。

  1. class Composite extends Component
  2. {
  3. //一个子对象集合用来存储其下属的枝节点和叶节点
  4. private $children = array();
  5. public function __construct($name)
  6. {
  7. parent::__construct($name);
  8. }
  9. public function Add($c)
  10. {
  11. $this->children[] = $c;
  12. }
  13. public function Remove($c)
  14. {
  15. foreach ($this->children as $key => $value) {
  16. if ($value === $c) {
  17. unset($this->children[$key]);
  18. }
  19. }
  20. }
  21. //显示其枝节点名称,并对其下级进行遍历
  22. public function Display($depth)
  23. {
  24. echo Str::String('-', $depth) . $this->name . PHP_EOL;
  25. foreach ($this->children as $value) {
  26. $value->Display($depth + 2);
  27. }
  28. }
  29. }

客户端代码,能通过 Component 接口操作组合部件的对象。

  1. public function compositeDemo()
  2. {
  3. //生成树根root,根上长出两叶 LeafA 和 LeafB
  4. $root = new CompositeDemo('root');
  5. $root->Add(new Leaf('Leaf A'));
  6. $root->Add(new Leaf('Leaf B'));
  7. //根上长出分枝 Composite X,分枝上也有两叶 LeafXA 和 LeafXB
  8. $comp = new CompositeDemo('Composite X');
  9. $comp->Add(new Leaf('Leaf XA'));
  10. $comp->Add(new Leaf('Leaf XB'));
  11. $root->Add($comp);
  12. //在 Composite X 上在长出分支 Composite XY,
  13. //分枝上也有两叶 LeafXYA 和 LeafXYB
  14. $comp2 = new CompositeDemo('Composite XY');
  15. $comp2->Add(new Leaf('Leaf XXA'));
  16. $comp2->Add(new Leaf('Leaf XYB'));
  17. $comp->Add($comp2);
  18. //根部又长出两叶 LeafC 和 LeafD,可惜 LeafD 没长牢,被风吹走了
  19. $root->Add(new Leaf('Leaf C'));
  20. $leaf = new Leaf('Leaf D');
  21. $root->Add($leaf);
  22. $root->Remove($leaf);
  23. //显示大树的样子
  24. $root->Display(1);
  25. }

结果显示

  1. -root
  2. ---Leaf A
  3. ---Leaf B
  4. ---Composite X
  5. -----Leaf XA
  6. -----Leaf XB
  7. -----Composite XY
  8. -------Leaf XXA
  9. -------Leaf XYB
  10. ---Leaf C

19.3透明方式与安全方式

当Leaf类当中也有 Add 和 Remove 方法时,这种方式叫做透明方式,也就是说在 Component 中声明所有有用来管理子对象的方法,其中包括 Add、 Remove等。这样实现 Component 接口的所有子类都具备了 Add 和 Remove。这样做的好处就是叶节点和枝节点对于外界没有区别,它们具备完全一致的行为接口。但问题也很明显,因为 Leaf 类本身不具备 Add()、Remove()方法的功能,所以实现它是没有意义的。
如果Leaf类当中不用 Add 和 Remove 方法,那么就需要安全方式,也就是在 Component 接口中不去声明 Add 和 Remove 方法,那么子类的 Leaf 也就不需要去实现它,而是在 Composite 声明所有用来管理子类对象的方法,这样做就不会出现刚才提到的问题,不过由于不够透明,所以树叶和树枝类将不具有相同的接口,客户端的调用需要做相应的判断,带来了不便。

19.4何时使用组合模式

当你发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。

19.5公司管理系统

代码结构图 第十九章 分公司=一部门——组合模式 - 图2代码实现
公司类 抽象类或接口

  1. abstract class Company
  2. {
  3. protected $name = '';
  4. public function __construct($name)
  5. {
  6. $this->name = $name;
  7. }
  8. abstract function Add($c); //增加
  9. abstract function Remove($c); //移除
  10. abstract function Display($depth); //显示
  11. abstract function LineOfDuty(); //履行职责
  12. }

具体公司类 实现接口 树枝节点

  1. class ConcreteCompany extends Company
  2. {
  3. private $children = array();
  4. public function __construct($name)
  5. {
  6. parent::__construct($name);
  7. }
  8. public function Add($c)
  9. {
  10. $this->children[] = $c;
  11. }
  12. public function Remove($c)
  13. {
  14. foreach ($this->children as $key => $value) {
  15. if ($value === $c) {
  16. unset($this->children[$key]);
  17. }
  18. }
  19. }
  20. public function Display($depth)
  21. {
  22. echo Str::String('-', $depth) . $this->name . PHP_EOL;
  23. foreach ($this->children as $value) {
  24. $value->Display($depth + 2);
  25. }
  26. }
  27. //履行职责
  28. public function LineOfDuty()
  29. {
  30. foreach ($this->children as $value) {
  31. $value->LineOfDuty();
  32. }
  33. }
  34. }

人力资源部与财务部类 树叶节点

  1. //人力资源部
  2. class HRDepartment extends Company
  3. {
  4. public function __construct($name)
  5. {
  6. parent::__construct($name);
  7. }
  8. public function Add($c)
  9. {
  10. // TODO: Implement Add() method.
  11. }
  12. public function Remove($c)
  13. {
  14. // TODO: Implement Remove() method.
  15. }
  16. public function Display($depth)
  17. {
  18. echo Str::String('-', $depth) . $this->name . PHP_EOL;
  19. }
  20. public function LineOfDuty()
  21. {
  22. echo $this->name . ' 员工招聘培训管理' . PHP_EOL;
  23. }
  24. }
  25. //财务部
  26. class FinanceDepartment extends Company
  27. {
  28. public function __construct($name)
  29. {
  30. parent::__construct($name);
  31. }
  32. public function Add($c)
  33. {
  34. // TODO: Implement Add() method.
  35. }
  36. public function Remove($c)
  37. {
  38. // TODO: Implement Remove() method.
  39. }
  40. public function Display($depth)
  41. {
  42. echo Str::String('-', $depth) . $this->name . PHP_EOL;
  43. }
  44. public function LineOfDuty()
  45. {
  46. echo $this->name . ' 公司财务收支管理' . PHP_EOL;
  47. }
  48. }

客户端调用

  1. public function compositeImp()
  2. {
  3. $root = new ConcreteCompany('北京总公司');
  4. $root->Add(new HRDepartment('总公司人力资源部'));
  5. $root->Add(new FinanceDepartment('总公司财务部'));
  6. $comp = new ConcreteCompany('上海华东分公司');
  7. $comp->Add(new HRDepartment('华东分公司人力资源部'));
  8. $comp->Add(new FinanceDepartment('华东分公司财务部'));
  9. $root->Add($comp);
  10. $comp1 = new ConcreteCompany('南京办事处');
  11. $comp1->Add(new HRDepartment('南京办事处人力资源部'));
  12. $comp1->Add(new FinanceDepartment('南京办事处财务部'));
  13. $comp->Add($comp1);
  14. $comp2 = new ConcreteCompany('杭州办事处');
  15. $comp2->Add(new HRDepartment('杭州办事处人力资源部'));
  16. $comp2->Add(new FinanceDepartment('杭州办事处财务部'));
  17. $comp->Add($comp2);
  18. echo '结构图:' . PHP_EOL;
  19. $root->Display(1);
  20. echo '职责:' . PHP_EOL;
  21. $root->LineOfDuty();
  22. }

结果显示

  1. 结构图:
  2. -北京总公司
  3. ---总公司人力资源部
  4. ---总公司财务部
  5. ---上海华东分公司
  6. -----华东分公司人力资源部
  7. -----华东分公司财务部
  8. -----南京办事处
  9. -------南京办事处人力资源部
  10. -------南京办事处财务部
  11. -----杭州办事处
  12. -------杭州办事处人力资源部
  13. -------杭州办事处财务部
  14. 职责:
  15. 总公司人力资源部 员工招聘培训管理
  16. 总公司财务部 公司财务收支管理
  17. 华东分公司人力资源部 员工招聘培训管理
  18. 华东分公司财务部 公司财务收支管理
  19. 南京办事处人力资源部 员工招聘培训管理
  20. 南京办事处财务部 公司财务收支管理
  21. 杭州办事处人力资源部 员工招聘培训管理
  22. 杭州办事处财务部 公司财务收支管理

19.6组合模式好处

组合模式的好处:

  • 组合模式这样就定义了包含人力资源部和财务部这些基本对象和分公司、办事处等组合对象的类层次结构。
  • 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象了。
  • 用户是不用关系到底是处理一个叶节点还是处理一个组合组件,也就用不着为定义组合而写一些选择判断语句了。

简单点说,就是组合模式让客户可以一致地使用组合结构和单个对象。