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.
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.
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" ); } }
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!!!
Artigo muito bom. Obrigado por compartilhar!
Opa! Disponha Rafael!!! Continue acessando, ok?