Ir ao conteúdo

Padrão de Projeto Observer em PHP com exemplo

O Padrão de Projeto Observer cria um relacionamento de um objeto notificador e muitos objetos que ficam observando-o para receber suas notificações quando o estado deste objeto notificador mudar.  Simplificando, o padrão Observer permite que um objeto notifique outros objetos sobre alterações em seu estado.

Diagrama de Classe UML do Padrão de Projeto Observer
Diagrama de Classe UML do Padrão de Projeto Observer

O que é um estado de um objeto? 

Em um objeto normalmente você tem propriedade e métodos. Quando você atribui valores concretos às propriedades, temos um estado. Ou seja, o conjunto de valores dos atributos de um determinado objeto é chamado de estado.

Relação do Subject com os Observers

Complementando o diagrama UML do padrão Observer, fiz um uma segunda representação onde conseguimos ver claramente que temos um Subject, que conhece um ou muitos Observers e quando o estado do  Subject tiver uma modificação, os Observers serão notificados

Diagrama com a Relação do Subject com os Observers
Diagrama com a Relação do Subject com os Observers

Um exemplo muito bom para para você compreender o uso padrão Observer, são as ações de inscrições do Youtube. Quando você se inscreve, você e os demais inscritos se tornam Observers e o Youtube o Subject. A cada vídeo novo (mudança de estado), vocês são notificados automaticamente.

A biblioteca Standard PHP Library(SPL) do PHP tem as interfaces para Subject (SplSubject) e Observer (SplObserver) que podemos utilizar normalmente. Essa biblioteca realmente tem muitos recursos úteis.

Primeiro vamos criar nossos Value Objects para representar o Video e os Assinantes. Só uma observação… No momento em que formos criar um Vídeo, nós não vamos precisar trocar seu título em tempo de execução, com isso, para passar novos conceitos, vou definir sua propriedade como Readonly ou seja propriedade somente leitura (Recurso nodo do PHP 8.1: Readonly Properties). 

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Observer;

final class Video
{
    public readonly string $title;

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

Vou aproveitar o exemplo e passar mais um novo recurso. O PHP também permite que você declare diretamente uma propriedade na assinatura do construtor, ao invés da declaração explicita como propriedade, assim como fizemos na classe Video.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Observer;

final class Subscriber
{
    public function __construct(
        public readonly string $email
    ) {}
}

Agora vamos criar o nosso objeto “observável” que terá seu estado observado. Vamos utilizar o SplObjecrStorage para construir nossa coleção de objetos observers.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Observer;

use SplObjectStorage;
use SplObserver;
use SplSubject;

final class VideoObservable implements SplSubject
{   
    public readonly Video $video;
    private SplObjectStorage $observers;

    public function __construct(Video $video)
    {
        $this->video = $video;
        $this->observers = new SplObjectStorage();  
    }

    public function attach(SplObserver $observer): void
    {
        $this->observers->attach($observer);
    }

    public function detach(SplObserver $observer): void
    {
        $this->observers->detach($observer);
    }

    public function notify(): void
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }    
}

Por fim vamos criar a classe para representar os nossos Observers

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\Observer;

use SplObserver;
use SplSubject;

class VideoObserver implements SplObserver
{
    private Subscriber $subscriber;

    public function __construct(Subscriber $subscriber)
    {
        $this->subscriber = $subscriber;
    }

    public function update(SplSubject $subject): void
    {
       printf(
           "%s has been notified of \"%s\"\n", 
            $this->subscriber->email, 
            $subject->video->title
        );
    }
}

Na implementação do teste, você conseguirá mesclar todos os recursos e compreender claramente a relação entre Subject e os Observers. Você tem o objeto Video, que se torna observável através do VideoObservable e você tem o observer, VideoObserver que está atrelado a alguma pessoa inscrita, Subscriber. Com isso, o VideoObservable cria uma estrutura contendo todos os Observers e pode tanto adicionar quanto remover. E no momento em que ele disparar a notificação, método notity, todos os Observers recebem a mensagem, exceto os removidos.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Behavioral\Observer;

use Growthdev\DesignPatterns\Behavioral\Observer\Video;
use Growthdev\DesignPatterns\Behavioral\Observer\Subscriber;
use Growthdev\DesignPatterns\Behavioral\Observer\VideoObservable;
use Growthdev\DesignPatterns\Behavioral\Observer\VideoObserver;
use PHPUnit\Framework\TestCase;

final class VideoObservableTest extends TestCase
{
    public function testShouldCreateVideoObservers(): void
    {
        $video = new Video('Video: Create Obsever Pattern');
        
        $anaObserver = new VideoObserver(new Subscriber('ana@email.com.br'));
        $mariaObserver = new VideoObserver(new Subscriber('maria@email.com.br'));
        $walmirObserver = new VideoObserver(new Subscriber('walmir@email.com.br'));
        $joaoObserver = new VideoObserver(new Subscriber('joao@email.com.br'));

        $videoObservable = new VideoObservable($video);
        $videoObservable->attach($anaObserver);
        $videoObservable->attach($mariaObserver);
        $videoObservable->attach($walmirObserver);
        $videoObservable->attach($joaoObserver);

        // remove observer from list
        $videoObservable->detach($mariaObserver);

        $videoObservable->notify();

        $this->expectOutputString(
            "ana@email.com.br has been notified of \"Video: Create Obsever Pattern\"\n"
            . "walmir@email.com.br has been notified of \"Video: Create Obsever Pattern\"\n"
            . "joao@email.com.br has been notified of \"Video: Create Obsever Pattern\"\n"
        );
    }
}
Resultado dos testes de uso do Padrão Observer
Resultado dos testes de uso do Padrão Observer

Quando usar o padrão Observer?

Toda vez que você tiver um objeto, que a modificação do seu estado implicará modificações em outro. Ou seja, quando um objeto tem a necessidade de notificar outros objetos sobre a mudança do seu estado.

O Padrão de Projeto Observer tem um baixo acoplamento. Ou seja, por mais que ele tenha uma relação de 1 para “n” objetos,  ele  encapsula os aspectos separadamente. Com isso, permite-se a reutilização independente dos objetos.

Os demais padrões de projetos do livro GOF estão disponíveis no artigo Resumo dos Padrões de Projetos (Design Patterns). Além disso, todos os códigos-fonte de todos os exemplos em PHP estão disponíveis no meu Github:

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

Espero que você tenha gostado deste artigo. Não deixe de retribuir compartilhando com outras pessoas para que eles possam evoluir também e meu blog crescer também. Até o próximo artigo!

Confiança Sempre!!!

Olá! Sou Walmir, engenheiro de software com MBA em Engenharia de Software e o cérebro por trás do GrowthCode e autor do livro "Além do Código". Se você acha que programação é apenas sobre escrever código, prepare-se para expandir seus horizontes. Aqui, nós vamos além do código e exploramos as interseções fascinantes entre tecnologia, negócios, artes e filosofia. Você está em busca de crescimento na carreira? Quer se destacar em um mercado competitivo? Almeja uma vida mais rica em conhecimento e realização? Então você chegou ao lugar certo. No GrowthCode, oferecemos insights profundos, estratégias comprovadas e um toque de sabedoria filosófica para catalisar seu crescimento pessoal e profissional.

Publicado emDesign PatternPadrões de ProjetosPHP

2 Comentários

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *