Executando verificação de segurança...
9

Entendendo o padrão Observer!

Falaaaa galera, meu primeiro post aqui será para compartilhar meu entendimento a cerca desse Design Pattern que tanto me fez esquentar a cuca.
(Apenas um adendo: ainda não sou desenvolvedor profissional, portanto peço que se virem algo que esteja errado ou que possa melhorar, me avisem para editar o post).

Mas afinal o que é esse tal Observer?

O padrão Observer é um Design Pattern do tipo comportamental, (padrões que tratam sobre a comunicação entre objetos), ele é muito util quando temos uma situação onde um ou varios objetos (dentro desse Pattern os chamamos de Observers) querem ser "avisados" sobre algo que ocorra com outro objeto (nesse contexto chamamos esse objeto de Subject)

Diagrama exemplificando Observer

A primeira vez que esbarrei nesse padrão foi num projeto de campo minado onde havia uma classe "Campo" que deveria ser notificada se outras instancias dessa classe fossem "abertas, marcadas, explodissem e etc", porém o exemplo que finalmente fez com que eu entendesse esse conceito foi o seguir:

Imagine o cenario do Youtube, onde temos canais que produzem conteudo, cada canal tem uma lista de inscritos que quer ser notificada quando aquele canal em questão publicar um video novo; assim sempre que um video for publicado naquele canal, o Youtube varre essa lista de inscritos notificando cada um do upload daquele video novo.

Mas como implementar isso na pratica?

De inicio iremos definir uma interface que conterá o metodo de notificação dos interessados:

public interface Observer {

    public void notificar(String canal);

}

Depois iremos definir uma classe concreta que irá implementar essa interface, no nosso exemplo do Youtube, será o Inscrito:

public class Inscrito implements Observer{

    private String nomeDoInscrito;

    @Override
    public void notificar(String canal) {
        System.out.println(nomeDoInscrito + " há um novo video no canal: " + canal);
    }

    public Inscrito(String nomeDoInscrito) {
        this.nomeDoInscrito = nomeDoInscrito;
    }
}

Agora iremos criar a classe que está sendo "ouvida" pelos Observers:

import java.util.ArrayList;
import java.util.List;

public class Canal {

    private String nomeDoCanal;
    private List<Observer> listaDeInscritos;

    public void registrarInscrito(Observer inscrito) {
        listaDeInscritos.add(inscrito);
    }

    public void removerInscrito(Observer inscrito) {
        listaDeInscritos.remove(inscrito);
    }

    public void notificarInscritos() {
        for (Observer inscrito: listaDeInscritos
             ) {
            // Repare que o Subject utiliza o proprio metodo dos inscritos para fazer a notificação:
            inscrito.notificar(this.getNomeDoCanal());
        }
    }

    public Canal(String nomeDoCanal) {
        this.listaDeInscritos = new ArrayList<>();
        this.nomeDoCanal = nomeDoCanal;
    }

    public String getNomeDoCanal() {
        return nomeDoCanal;
    }

}

Agora vamos implementar um exemplo pratico para ver o funcionamento desse Pattern:

public class Main {
    public static void main(String[] args) {

        Canal devIniciante = new Canal("Dev Iniciante");
        Inscrito gustavo = new Inscrito("Gustavo");
        Inscrito karolayne = new Inscrito("Karolayne");
        Inscrito franklin = new Inscrito("Franklin");
        Inscrito diego = new Inscrito("Diego");

        devIniciante.registrarInscrito(gustavo);
        devIniciante.registrarInscrito(karolayne);
        devIniciante.registrarInscrito(franklin);
        devIniciante.registrarInscrito(diego);
        devIniciante.notificarInscritos();

    }
}

O codigo acima terá uma saída no console exatamente igual a essa:
Gustavo há um novo video no canal: Dev Iniciante
Karolayne há um novo video no canal: Dev Iniciante
Franklin há um novo video no canal: Dev Iniciante
Diego há um novo video no canal: Dev Iniciante

Vantagem de utilizar o Observer?

Geralmente quando utilizamos o padrão Observer, temos uma "convenção" com os metodos a serem implementados que costumam levar os nomes de:

  • notify( ): Quando queremos que os observadores sejam notificados.
  • execute( ): Quando queremos que uma ação seja executada para cada observador.
  • update( ): Quando queremos que o estado, atributo e entre outros do observador seja alterado.

As vantagens desse Pattern se encontram em poder manipular um grande volume de objetos sempre que o gatilho para isso seja um Evento de Interesse, outro ponto importante é que utilizando do polimorfismo, pelo fato de a classe Observer ser uma interface, cada classe concreta pode implementar seu metodo para executar da maneira que melhor lhe couber. Por exemplo:

  • Uma classe Cliente é uma Observer da classe Promoção.
  • A classe Produto também é um Observer da classe Promoção.
  • Porém a classe Cliente a partir da criação de uma nova promoção será notificada da mesma, enquanto que a classe Produto poderá ter uma alteração no seu preço conforme o desconto da tal promoção.

Espero que os exemplos acima tenham lhe ajudado a entender o funcionamento desse padrão! Como disse anteriormente, sou um dev em transição de carreira, portanto estou aberto a correções e melhorias!

2

Muito bacana.

Tem alguns insigths aproveitando seu trabalho.

No java com springboot tu pode usar um evento, deixando essa chamada mais desacoplada do teu código pois quem emite o evento não precisa conhecer quem vai ouvir. Confesso que não sei se o nome continuaria chamando Observer mas seria algo como:

  1. Um cara que vai ser o representante de eventos de um canal
import org.springframework.context.ApplicationEvent;

public class ChannelEvent extends ApplicationEvent {

    public ChanelEvent(EventType event) {
        super(event);
    }

}

Tua classe canal

import org.springframework.context.ApplicationEventPublisher;
    
public class Canal {
    private final ApplicationEventPublisher eventPublisher;
    private String nomeDoCanal;
    private List<Observer> listaDeInscritos;

.....

    public void notificarInscritos() {
    //aqui tu dispara o evento
        eventPublisher.publishEvent(new ChannelEvent(EventType.NEW_MOVIE));
    }

.....

}

Por fim uma service que vai ficar escutando os eventos. Tu pode ter 1 ou n services de propósitos diferentes:

    @EventListener
    public void onPath(final ChanelEvent event) {
         if(EventType.NEW_MOVIE.equals(event)) {
            // faz alguma coisa como buscar no banco todos os insvcritos e enviar uma notificação. Neste caso mandei só um enum mas poderia ter mandado um objeto com mais informações.
         }
    }

Como pode ver, a vantagem desta implementação é que você cria um evento e ele estará sempre disponível em todo seu projeto. Pense que poderia ser um evento de banco de dados. Sempre que eu salvar um objeto específico quero validar se ele atende algum critério. Mas o principal é que tu mantem desacoplado o teu código. A classe Chanel não tem obrigação de saber quem quer ser notificado quando um novo video for lançado. No teu caso, tu falou de inscritos mas poderiamos ter uma outra service que iria ouvir novos vídeos para classificar eles por relevancia , ou para aplicar um processamento de video em busca de irregularidades. Obvio, citei coisas que talvez sejam resolvidas melhor num microservice mas deu pra entender.

3

Fala, @JonasRR93. Não sou dev Java, mas só pela sua descrição inicial sobre como funciona esse mecanismo de Event, já entendo que ele é uma implementação do famoso padrão Pub/Sub.
A maioria das pessoas confundem o Pub/Sub com o Observer (já tive que responder isso em entrevista de trabalho), mas a principal diferença entre eles é que no Observer, o Sujeito conhece os Observadores e no Pub/Sub você possui um barramento de eventos e os Interessados escutam os eventos desse barramento.

1

Valeu Jonas, muito legal sua abordagem ainda mais se tratando do Spring que estou me aprofundando no momento! Vou tentar implementar em algum projeto futuramente.

2

Fala Gustavo! Muito massa!

O Observer é um padrão muito útil mesmo. Recentemente precisei utilizá-lo no Lixlr para comunicar alterações no estado da aplicação. Basicamente quando o React altera seu estado (pelo react-tracked), eu preciso comunicar todo o Core da aplicação sobre essa mudança, pois o Core funciona separadamente do React.

Também tem um site muito bom sobre padrões de design, Refactoring.Guru.

Os códigos estão aqui:
https://github.com/dhrleandro/lixlr/blob/main/src/core/state/AbstractStateObserver.ts
https://github.com/dhrleandro/lixlr/blob/main/src/core/state/StateManager.ts
https://github.com/dhrleandro/lixlr/blob/main/src/core/state/SubjectObserver.ts
https://github.com/dhrleandro/lixlr/blob/main/src/core/App.ts

1

Muito obrigado pela recomendação Leandro! Vai me ajudar muito nos meus estudos! E adorei sua aplicação, tenho muita vontade de aprender a mexer com essa parte mais visual do front, porém tenho muito trabalho no backend ainda para aprender rs

1
2

Obrigado @Hanufu, existem sim, se dividem em 3 tipos:

  • Criacionais
  • Estruturais
  • Comportamentais

Eu ja implementei 3 padrões em projetos pessoais, o próprio Observer, o Singleton e o Chain of Responsability.

O @leandrodaher indicou abaixo um
site que curti muito para aprender sobre Patterns.

1

O padrão Observer é extremamente útil em diversos contextos. No exemplo acima, ele permite que vários objetos sejam notificados de forma eficiente quando um evento ocorre. Como a interface Observer é utilizada, é possível ter diversas implementações do mesmo, permitindo que cada objeto agrupe apenas as notificações que realmente lhe interessam.