2.5. Dekorator (Decorator)

2.5.1. Przeznaczenie

Pozwala na dynamiczne dodawanie nowych funkcji do istniejących klas podczas działania programu.

2.5.2. Przykłady

  • Zend Framework: dekoratory do obiektów klasy Zend_Form_Element.
  • Web Service Layer: Dekorator typu JSON i XML używany w usłudze typu REST (w tym przykładzie tylko jeden z nich powinien być dozwolony).

2.5.3. Diagram UML

Alt Decorator UML Diagram

2.5.4. Kod

Ten kod znajdziesz również na GitHub.

RenderableInterface.php

1
2
3
4
5
6
7
8
<?php

namespace DesignPatterns\Structural\Decorator;

interface RenderableInterface
{
    public function renderData(): string;
}

Webservice.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace DesignPatterns\Structural\Decorator;

class Webservice implements RenderableInterface
{
    /**
     * @var string
     */
    private $data;

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

    public function renderData(): string
    {
        return $this->data;
    }
}

RendererDecorator.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
<?php

namespace DesignPatterns\Structural\Decorator;

/**
 * the Decorator MUST implement the RenderableInterface contract, this is the key-feature
 * of this design pattern. If not, this is no longer a Decorator but just a dumb
 * wrapper.
 */
abstract class RendererDecorator implements RenderableInterface
{
    /**
     * @var RenderableInterface
     */
    protected $wrapped;

    /**
     * @param RenderableInterface $renderer
     */
    public function __construct(RenderableInterface $renderer)
    {
        $this->wrapped = $renderer;
    }
}

XmlRenderer.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php

namespace DesignPatterns\Structural\Decorator;

class XmlRenderer extends RendererDecorator
{
    public function renderData(): string
    {
        $doc = new \DOMDocument();
        $data = $this->wrapped->renderData();
        $doc->appendChild($doc->createElement('content', $data));

        return $doc->saveXML();
    }
}

JsonRenderer.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php

namespace DesignPatterns\Structural\Decorator;

class JsonRenderer extends RendererDecorator
{
    public function renderData(): string
    {
        return json_encode($this->wrapped->renderData());
    }
}

2.5.5. Testy

Tests/DecoratorTest.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
<?php

namespace DesignPatterns\Structural\Decorator\Tests;

use DesignPatterns\Structural\Decorator;
use PHPUnit\Framework\TestCase;

class DecoratorTest extends TestCase
{
    /**
     * @var Decorator\Webservice
     */
    private $service;

    protected function setUp()
    {
        $this->service = new Decorator\Webservice('foobar');
    }

    public function testJsonDecorator()
    {
        $service = new Decorator\JsonRenderer($this->service);

        $this->assertEquals('"foobar"', $service->renderData());
    }

    public function testXmlDecorator()
    {
        $service = new Decorator\XmlRenderer($this->service);

        $this->assertXmlStringEqualsXmlString('<?xml version="1.0"?><content>foobar</content>', $service->renderData());
    }
}