Observer Pattern em TypeScript: Monitorando Telegram

Introdução 💻

Recentemente, comecei a estudar mais sobre Design Patterns e percebi que conhecer e usar um Design Pattern é praticamente criar um guia para seu software não se perder. Descobri isso na prática em um trabalho freelance que peguei. Vou falar na prática como ele ajuda, pois será mais fácil de entenderem o motivo do título do que explicar o que é um Design Pattern de fato.

Um cliente queria receber mensagens no Telegram e monitorar especificamente as que começassem com algum prefixo como "Olá", "Tchau" etc., para notificá-lo via WhatsApp ou Discord. Até então, tudo bem. Escolhi o TypeScript para essa função.

O problema que encontrei no meio do caminho foi como monitorar as mensagens recebidas pela biblioteca que escolhi (GramJS) com o seguinte código:

client.addEventHandler((event: NewMessageEvent) => {
    console.log('Opa, mensagem recebida: ' + event.message.message);
}, new NewMessage({}));

Imaginei o seguinte: meu código principal estava em /src/index.ts, enquanto a classe que iniciava o serviço do Telegram estava em /src/services/telegram.ts. Como o meu index.ts iria receber esse evento que vem do telegram.ts sem bagunçar meu código?

Pensei em enviar uma função como argumento para a classe que inicializa o cliente do Telegram, e até funcionaria, porém, queria algo mais organizado. Também poderia retornar o cliente do Telegram em uma variável, assim, ao iniciar a instância da classe telegram.ts, retornaria esse cliente. Porém, também queria algo mais elegante, para que meu index.ts ficasse responsável apenas por "instanciar" as coisas e o software rodar.

Observer Pattern 🔭

Encontrei um pattern que já tinha utilizado antes, mas na época não sabia que estava utilizando esse Design Pattern.

Basicamente, este Design Pattern consiste em criar uma classe contendo observadores que, quando um evento X disparar, esses observadores serão notificados. Pode ser que o observador n° 1 imprima uma mensagem, o observador n° 2 envie um e-mail, e tudo isso partindo de um evento disparado da classe principal que contém os observadores adicionados.

Mão no Código 👨‍💻

Vamos usar Typescript.

1. Crie uma interface Observer:

export interface Observer {
    onNewEvent(): void;
}

export class MyObserver implements Observer {
    onNewEvent(): void {
        console.log('Opa, observer notificado!');
    }
}

2. Crie uma classe com observadores:

export class MyClass {
    private observers: Observer[] = [];
    
    public addObserver(observer: Observer): void {
        this.observers.push(observer);
    }
    
    public removeObserver(observer: Observer): void {
        const index = this.observers.indexOf(observer);
        if (index !== -1) {
            this.observers.splice(index, 1);
        }
    }
    
    public notifyObservers(): void {
        for (const observer of this.observers) {
            observer.onNewEvent();
        }
    }
}

3. Inicie a classe e adicione os observadores:

const myClass = new MyClass();
myClass.addObserver(new MyObserver());

E então, aqui está pronto o Observer Pattern. Claro que não há nenhuma função que dispare os observadores neste caso, pois não criei nenhuma lógica para que eles sejam disparados. No meu caso do Telegram, sempre que chegava uma mensagem, era disparado na classe do Telegram para que notificasse os observadores. Eles recebiam como argumento o evento que ocorreu no Telegram e cada um tratava da forma necessária no software. Para notificar os observadores na nossa classe, basta, a partir de algum evento necessário, chamar o método notifyObservers() que criamos.

Pode parecer um pouco complexo. Para mim também pareceu a primeira vez que utilizei e nem sabia o motivo de criar essa complexidade no código. Hoje, após alguns meses de ter utilizado o padrão em um projeto da Rocketseat, entendi a necessidade dele.

Observação: lembrando que eu comecei recentemente levar a sério estudos sobre Design Pattern e estou espalhando conhecimento para ajudar e aprender também. Qualquer possível erro estou aberto a críticas construtivas ;)

Fico imensamente feliz pela comunidade de JavaScript/Typescript esta se interessando por design partner. Para quem é engenheiro de software, e vem de uma linguagem de programação mais estruturada e organizada, sente muita dificuldade em implementar os padrões de projeto, tanto por "limitações" da linguagem quando por falta de adesão e conhecimento dos devs.

eu mesmo tenho tentado aplicar design partner com typescript, e não é prático nem intuitivo.

Ainda não me considero um dev experiente em typescript, pois ainda me falta os macetes da linguagem.

Acho que depende do pattern e da aplicação e a forma que foi construida, mas todos que já vi consegui aplicar normalmente. Mas se lembre, foram criados pensando no paradgima OO e não funcional, alguns vc vai ter que fazer adaptações.

Estudar/utilizar Design Pattern é uma das melhores coisas que tenho aprendido nos últimos tempos. É impressionante como deixa o código mais limpo, organizável e, talvez o mais importante, fácil de fazer modificações.

Apliquei o observer recentemente também é ridículo o quanto que ele facilitar caso eu queira adicionar/remover reação a um determinado evento é realmente muito bom.

Sim, utilizar Design Pattern é como um guia para o software. Você tem um problema e a maioria das vezes já foi solucionado com algum Design Pattern.

Parece interessante para fazer self-healing. Normalmente esse design pattern é construído pensando nisso? Ou o uso é mais variado?

Ele serviria bem para self-healing porém a origem da criação dele não encontrei fontes para saber se foi construído pensando nisso. O uso dele é comumente usado como solução para variados tipos de problemas.