Ir ao conteúdo

Padrão de Projeto Mediator em PHP com exemplo

O Padrão de Projeto Mediator é muito utilizado quando temos muitos relacionamentos entre objetos. Com isso, ele assume o comando e faz a mediação entre os  objetos. Ou seja, ele restringe as comunicações diretas entre os objetos e mediador entra em ação para assumir essa responsabilidade.

“Definir um objeto que encapsula a forma como um conjunto de objetos interage. O Mediator promove o acoplamento fraco ao evitar que os objetos se refiram uns aos outros explicitamente e permite variar suas interações independentemente.”

(Gamma)
Diagrama de Classe Padrão de Projeto Mediator
Diagrama de Classe Padrão de Projeto Mediator

Olhando esse diagrama de classe parece um pouco confuso o uso do Padrão Mediator. Porém, conseguimos observar que temos claramente os Mediadores e os Colaboradores. É um jogo de comunicação simples. A interface do Mediador é usada pelos colaboradores para iniciar a comunicação e receber notificações e o Mediador recebe e repassa as requisições para os destinatários.

Fluxo de mensagem padrão Mediator
Fluxo de mensagem padrão Mediator

Um excelente exemplo para exemplificar o uso deste padrão. Pense em um aeroporto, por exemplo, o de Guarulhos (GRU) em São Paulo. Temos vários aviões que trafegam e precisam em algum momento se comunicarem para estarem em sincronismo para não gerar acidentes ou imprevistos. Só que imagina 10 aviões ao mesmo tempo tendo comunicação direta entre os pilotos. Tudo isso geraria um caos. Por este motivo, eles têm o controlador de tráfego aéreo que faz o papel de Mediator e controla a comunicação entre as aeronaves.

Fiz uma representação em um diagrama de classe onde você poderá comparar as classes e como funcionam os seus relacionamentos:

Exemplo Diagrama Mediator
Exemplo Diagrama Mediator

Vamos ao código então! Começando pelas classes abstratas e interfaces que gerarão os contratos de comunicação entre os participantes:

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Mediator;

interface AirTrafficControl
{
    public function addAirplane(Airplane $airplane): void;

    public function notifyAirplane(Airplane $airplane, string $message): void;
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Mediator;

abstract class Airplane
{
    protected AirTrafficControl $airTrafficControl;
    protected string $message;

    public function __construct(AirTrafficControl $airTrafficControl)
    {
        $this->airTrafficControl = $airTrafficControl;
    }

    abstract public function sendMessage(string $message): void;

    abstract public function receiveMessage(string $message): void;

    public function getMessage(): string
    {
        return $this->message;
    }
}

Agora representamos o GRU Air Traffic Control como Concrete Mediator e as classes Airbus, Boing e Cessna como Concrete Colleagues.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Mediator;

use SplObjectStorage;

final class GRUAirTrafficControl implements AirTrafficControl
{
    private SplObjectStorage $airplane;

    public function __construct()
    {
        $this->airplane = new SplObjectStorage();
    }

    public function addAirplane(Airplane $airplane): void
    {
        $this->airplane->attach($airplane);
    }

    public function notifyAirplane(Airplane $airplaneReceiver, string $message): void
    {
        while ($this->airplane->valid()) {
            $airplane = $this->airplane->current();
            if ($airplane !== $airplaneReceiver) {
                $airplane->receiveMessage($message);
            }
            
            $this->airplane->next();
        }
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Mediator;

class Airbus extends Airplane
{
    public function sendMessage(string $message): void
    {
        $this->airTrafficControl->notifyAirplane($this, $message);
    }

    public function receiveMessage(string $message): void
    {
        $this->message = sprintf("Airbus received message: %s", $message);
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Mediator;

class Boing extends Airplane
{
    public function sendMessage(string $message): void
    {
        $this->airTrafficControl->notifyAirplane($this, $message);
    }

    public function receiveMessage(string $message): void
    {
        $this->message = sprintf("Boing received message: %s", $message);
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Mediator;

class Cessna extends Airplane
{
    public function sendMessage(string $message): void
    {
        $this->airTrafficControl->notifyAirplane($this, $message);
    }

    public function receiveMessage(string $message): void
    {
        $this->message = sprintf("Cessna received message: %s", $message);
    }
}

A relação de comunicação efetiva com o Mediador você consegue observar através da implementação dos testes.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Behavioral\Mediator;

use Growthdev\DesignPatterns\Behavioral\Mediator\Airbus;
use Growthdev\DesignPatterns\Behavioral\Mediator\Boing;
use Growthdev\DesignPatterns\Behavioral\Mediator\Cessna;
use Growthdev\DesignPatterns\Behavioral\Mediator\GRUAirTrafficControl;
use PHPUnit\Framework\TestCase;

final class AirTrafficControlTest extends TestCase
{
    public function testAirTrafficControlOfTheBoingSendingMessage(): void
    {
        $airTrafficControl = new GRUAirTrafficControl();
        $boing = new Boing($airTrafficControl);
        $airbus = new Airbus($airTrafficControl);
        $cessna = new Cessna($airTrafficControl);

        $airTrafficControl->addAirplane($boing);
        $airTrafficControl->addAirplane($airbus);
        $airTrafficControl->addAirplane($cessna);

        $boing->sendMessage("BOING: I am flying!");

        $this->assertEquals('Airbus received message: BOING: I am flying!', $airbus->getMessage());
        $this->assertEquals('Cessna received message: BOING: I am flying!', $cessna->getMessage());
    }

    public function testAirTrafficControlOfTheAirbusSendingMessage(): void
    {
        $airTrafficControl = new GRUAirTrafficControl();
        $boing = new Boing($airTrafficControl);
        $airbus = new Airbus($airTrafficControl);
        $cessna = new Cessna($airTrafficControl);

        $airTrafficControl->addAirplane($boing);
        $airTrafficControl->addAirplane($airbus);
        $airTrafficControl->addAirplane($cessna);

        $airbus->sendMessage("AIRBUS: I am flying!");

        $this->assertEquals('Boing received message: AIRBUS: I am flying!', $boing->getMessage());
        $this->assertEquals('Cessna received message: AIRBUS: I am flying!', $cessna->getMessage());
    }

    public function testAirTrafficControlOfTheCessnaSendingMessage(): void
    {
        $airTrafficControl = new GRUAirTrafficControl();
        $boing = new Boing($airTrafficControl);
        $airbus = new Airbus($airTrafficControl);
        $cessna = new Cessna($airTrafficControl);

        $airTrafficControl->addAirplane($boing);
        $airTrafficControl->addAirplane($airbus);
        $airTrafficControl->addAirplane($cessna);

        $cessna->sendMessage("CESSNA: I am flying!");

        $this->assertEquals('Boing received message: CESSNA: I am flying!', $boing->getMessage());
        $this->assertEquals('Airbus received message: CESSNA: I am flying!', $airbus->getMessage());
    }
}

Se você estiver acompanhando esta série de artigos sobre Padrões de Projetos. Você vai se deparar com padrões muitos similares. E dependendo da forma como você utilizar, você pode estar transformando um padrão em outro sem nem perceber. Por exemplo, se você tiver apenas um mediador ativo e as classes Collegues forem passivas, você acaba transformando sua implementação no Padrão Facade. Fique atento a estes detalhes.

Vantagens e desvantagens do Padrão Mediator

Como você pode perceber, o Single Responsibility Principle (SRP) é um ponto forte deste pattern além do é Open Closed Principle (OCP) que nos dá facilidade em introduzir novos mediadores assim como novos Collegues sem comprometer o que já existe.

No meu ponto de vista a principal desvantagem deste padrão é que você delega muita responsabilidade para um indivíduo mediador. Isso pode aumentar sua complexidade e o transformar em um God Object. De toda forma, sabemos que nada é perfeito. Então, usando com sabedoria temos bons frutos com o uso deste pattern.

Todos os exemplos de todos os artigos sobre Padrões de Projetos, você encontra no meu Gitgub:

https://github.com/growthdev-repo/design-patterns

Espero que você tenha tirado proveito deste artigo e não deixe de comentar e compartilhar com outros profissionais para que eles possam se beneficiar deste conteúdo assim como você se beneficiou. Até o próximo artigo!

Confiança Sempre!!!

Fontes:

Publicado emDesign PatternPadrões de Projetos

Seja o primeiro a comentar

Deixe um comentário

O seu endereço de e-mail não será publicado.