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 ;)