2.3. 组合模式
2.3.1. 目的
以单个对象的方式来对待一组对象
2.3.2. 例子
form类的实例包含多个子元素,而它也像单个子元素那样响应render()请求,当调用
render()
方法时,它会历遍所有的子元素,调用render()
方法
2.3.3. UML 图

2.3.4. 代码
你可以在 GitHub 上找到这些代码
Renderable.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Composite;
6
7interface Renderable
8{
9 public function render(): string;
10}
Form.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Composite;
6
7/**
8 * The composite node MUST extend the component contract. This is mandatory for building
9 * a tree of components.
10 */
11class Form implements Renderable
12{
13 /**
14 * @var Renderable[]
15 */
16 private array $elements;
17
18 /**
19 * runs through all elements and calls render() on them, then returns the complete representation
20 * of the form.
21 *
22 * from the outside, one will not see this and the form will act like a single object instance
23 */
24 public function render(): string
25 {
26 $formCode = '<form>';
27
28 foreach ($this->elements as $element) {
29 $formCode .= $element->render();
30 }
31
32 return $formCode . '</form>';
33 }
34
35 public function addElement(Renderable $element)
36 {
37 $this->elements[] = $element;
38 }
39}
InputElement.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Composite;
6
7class InputElement implements Renderable
8{
9 public function render(): string
10 {
11 return '<input type="text" />';
12 }
13}
TextElement.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Composite;
6
7class TextElement implements Renderable
8{
9 public function __construct(private string $text)
10 {
11 }
12
13 public function render(): string
14 {
15 return $this->text;
16 }
17}
2.3.5. 测试
Tests/CompositeTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Structural\Composite\Tests;
6
7use DesignPatterns\Structural\Composite\Form;
8use DesignPatterns\Structural\Composite\TextElement;
9use DesignPatterns\Structural\Composite\InputElement;
10use PHPUnit\Framework\TestCase;
11
12class CompositeTest extends TestCase
13{
14 public function testRender()
15 {
16 $form = new Form();
17 $form->addElement(new TextElement('Email:'));
18 $form->addElement(new InputElement());
19 $embed = new Form();
20 $embed->addElement(new TextElement('Password:'));
21 $embed->addElement(new InputElement());
22 $form->addElement($embed);
23
24 // This is just an example, in a real world scenario it is important to remember that web browsers do not
25 // currently support nested forms
26
27 $this->assertSame(
28 '<form>Email:<input type="text" /><form>Password:<input type="text" /></form></form>',
29 $form->render()
30 );
31 }
32}