参考:https://www.phpmianshi.com/?id=129
概念
当我们的一个对象可能代表一个单一的实体,或者一个组合的实体,但是仍然需要通过同样的方式被使用时,这种情形则适合使用组合模式的设计。
示例
一个表单类实例在处理其表单所有元素的方法与处理该表单自身实例方法相同,在调用方法 render() 时,会随之遍历它的所有子元素并对他们调用 render() 方法
<?php interface RenderableInterface { public function render(): string; } /** * 该组合内的节点必须派生于该组件契约。为了构建成一个组件树, * 此为强制性操作。 */ class Form implements RenderableInterface { /** * @var RenderableInterface[] */ private $elements; /** * 遍历所有元素,并对他们调用 render() 方法,然后返回表单的完整 * 的解析表达。 * * 从外部上看,我们不会看到遍历过程,该表单的操作过程与单一对 * 象实例一样 * * @return string */ public function render(): string { $formCode = '<form>'; foreach ($this->elements as $element) { $formCode .= $element->render(); } $formCode .= '</form>'; return $formCode; } /** * @param RenderableInterface $element */ public function addElement(RenderableInterface $element) { $this->elements[] = $element; } } class InputElement implements RenderableInterface { public function render(): string { return '<input type="text" />'; } } class TextElement implements RenderableInterface { /** * @var string */ private $text; public function __construct(string $text) { $this->text = $text; } public function render(): string { return $this->text; } } $form = new Form(); $form->addElement(new TextElement('Email:')); $form->addElement(new InputElement()); $embed = new Form(); $embed->addElement(new TextElement('Password:')); $embed->addElement(new InputElement()); $form->addElement($embed);
示例2
<?php /** * 组合模式抽象基类 */ abstract class CompanyBase{ //节点名称 protected $name; public function __construct($name){ $this->name = $name; } public function getName(){ return $this->name; } //增加节点 abstract function add(CompanyBase $c); //删除节点 abstract function remove(CompanyBase $c); //输出节点信息 abstract function show($deep); //节点职责 abstract function work($deep); } /** * 公司类 */ class Company extends CompanyBase{ protected $item = []; public function add(CompanyBase $c){ $nodeName = $c->getName(); if(!isset( $this->item[$nodeName] )){ $this->item[$nodeName] = $c; }else{ throw new Exception("该节点已存在,节点名称:".$nodeName); } } public function remove(CompanyBase $c){ $nodeName = $c->getName(); if(isset( $this->item[$nodeName] )){ unset($this->item[$nodeName]); }else{ throw new Exception("该节点不存在,节点名称:".$nodeName); } } public function show($deep = 0){ echo str_repeat("-",$deep).$this->name; echo "<br>"; foreach($this->item as $value){ $value->show($deep+4); } } public function work($deep = 0){ foreach($this->item as $value){ echo str_repeat(" ",$deep)."[{$this->name}]<br>"; $value->work($deep+2); } } } /** * 人力资源部门 */ class HumanResources extends CompanyBase{ public function add(CompanyBase $c){ throw new Exception("该节点下不能增加节点"); } public function remove(CompanyBase $c){ throw new Exception("该节点下无子节点"); } public function show($deep = 0){ echo str_repeat("-",$deep).$this->name; echo "<br>"; } public function work($deep = 0){ echo str_repeat(" ",$deep)."人力资源部门的工作是为公司招聘人才"; echo "<br>"; } } /** * 商务部门 */ class Commerce extends CompanyBase{ public function add(CompanyBase $c){ throw new Exception("该节点下不能增加节点"); } public function remove(CompanyBase $c){ throw new Exception("该节点下无子节点"); } public function show($deep = 0){ echo str_repeat("-",$deep).$this->name; echo "<br>"; } public function work($deep = 0){ echo str_repeat(" ",$deep)."商务部门的工作是为公司赚取利润"; echo "<br>"; } } $c = new Company("北京某科技公司"); $h = new HumanResources("人力资源部门"); $com = new Commerce("商务部门"); $c->add($h); $c->add($com); //天津分公司 //为了偷懒,分公司的部门直接copy母公司的 $c1 = new Company("天津分公司"); $c1->add($h); $c1->add($com); $c->add($c1); //武汉分公司 $c2 = new Company("武汉分公司"); $c2->add($h); $c2->add($com); $c->add($c2); //使用公司功能 $c->show(); $c->work();
总结组合模式的特点
-
必须存在不可分割基本元素;
-
组合后的物体仍然可以被组合。