2.7. Fasada (Facade)

2.7.1. Przeznaczenie

The primary goal of a Facade Pattern is not to avoid you having to read the manual of a complex API. It’s only a side-effect. The first goal is to reduce coupling and follow the Law of Demeter.

Fasada pozwala oddzielić klienta od podsystemu, poprzez udostępnienie jednego lub wielu interfejsów oraz zredukować złożoność rozwiązania.

  • Fasada nie zabrania bezpośredniego dostępu do podsystemu.
  • Dobrym pomysłem jest posiadanie wielu fasad do jednego podsystemu.

Dlatego dobra fasada nie tworzy nowych obiektów, tylko korzysta z istniejących (nie używa operatora new). Jeżeli w ramach fasady tworzymy wiele obiektów, w ramach każdej metody, wtedy nie mamy do czynienia z Fasadą, tylko z Budowniczym lub formą Fabryki.

W ramach Fasady najlepiej nie używać operatora new oraz definiować typy argumentów konstruktora (type-hinting). Jeżeli potrzebujemy stworzyć nowe obiekty, powinniśmy przekazać Fabrykę jako argument.

2.7.2. Diagram UML

Alt Facade UML Diagram

2.7.3. Kod

Ten kod znajdziesz również na GitHub.

Facade.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

namespace DesignPatterns\Structural\Facade;

class Facade
{
    /**
     * @var OsInterface
     */
    private $os;

    /**
     * @var BiosInterface
     */
    private $bios;

    /**
     * @param BiosInterface $bios
     * @param OsInterface   $os
     */
    public function __construct(BiosInterface $bios, OsInterface $os)
    {
        $this->bios = $bios;
        $this->os = $os;
    }

    public function turnOn()
    {
        $this->bios->execute();
        $this->bios->waitForKeyPress();
        $this->bios->launch($this->os);
    }

    public function turnOff()
    {
        $this->os->halt();
        $this->bios->powerDown();
    }
}

OsInterface.php

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

namespace DesignPatterns\Structural\Facade;

interface OsInterface
{
    public function halt();

    public function getName(): string;
}

BiosInterface.php

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

namespace DesignPatterns\Structural\Facade;

interface BiosInterface
{
    public function execute();

    public function waitForKeyPress();

    public function launch(OsInterface $os);

    public function powerDown();
}

2.7.4. Testy

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

namespace DesignPatterns\Structural\Facade\Tests;

use DesignPatterns\Structural\Facade\Facade;
use DesignPatterns\Structural\Facade\OsInterface;
use PHPUnit\Framework\TestCase;

class FacadeTest extends TestCase
{
    public function testComputerOn()
    {
        /** @var OsInterface|\PHPUnit_Framework_MockObject_MockObject $os */
        $os = $this->createMock('DesignPatterns\Structural\Facade\OsInterface');

        $os->method('getName')
            ->will($this->returnValue('Linux'));

        $bios = $this->getMockBuilder('DesignPatterns\Structural\Facade\BiosInterface')
            ->setMethods(['launch', 'execute', 'waitForKeyPress'])
            ->disableAutoload()
            ->getMock();

        $bios->expects($this->once())
            ->method('launch')
            ->with($os);

        $facade = new Facade($bios, $os);

        // the facade interface is simple
        $facade->turnOn();

        // but you can also access the underlying components
        $this->assertEquals('Linux', $os->getName());
    }
}