组合模式

目的

把一组对象视为和单个对象实例拥有一样的行为。

应用场景

  • 一个表单类,用处理表单实例的方式来处理所有表单元素。当调用表单的 render() 方法时,随后它会对其所有子元素运行 render() 方法

UML 类图

组合模式类图

代码

Renderable.php

1
2
3
4
5
6
7
8
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Composite;

interface Renderable
{
public function render(): string;
}

Form.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Composite;

/**
* The composite node MUST extend the component contract. This is mandatory for building
* a tree of components.
*/
class Form implements Renderable
{
/**
* @var Renderable[]
*/
private array $elements;

/**
* runs through all elements and calls render() on them, then returns the complete representation
* of the form.
*
* from the outside, one will not see this and the form will act like a single object instance
*/
public function render(): string
{
$formCode = '<form>';

foreach ($this->elements as $element) {
$formCode .= $element->render();
}

$formCode .= '</form>';

return $formCode;
}

public function addElement(Renderable $element)
{
$this->elements[] = $element;
}
}

InputElement.php

1
2
3
4
5
6
7
8
9
10
11
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Composite;

class InputElement implements Renderable
{
public function render(): string
{
return '<input type="text" />';
}
}

TextElement.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Composite;

class TextElement implements Renderable
{
private string $text;

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

public function render(): string
{
return $this->text;
}
}

测试

Tests/CompositeTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php declare(strict_types = 1);

namespace DesignPatterns\Structural\Composite\Tests;

use DesignPatterns\Structural\Composite\Form;
use DesignPatterns\Structural\Composite\TextElement;
use DesignPatterns\Structural\Composite\InputElement;
use PHPUnit\Framework\TestCase;

class CompositeTest extends TestCase
{
public function testRender()
{
$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);

// This is just an example, in a real world scenario it is important to remember that web browsers do not
// currently support nested forms

$this->assertSame(
'<form>Email:<input type="text" /><form>Password:<input type="text" /></form></form>',
$form->render()
);
}
}