Observer e Factory Patterns em Javascript

Gustavo Valle
Mercafacil
Published in
5 min readJul 21, 2023

Neste artigo, iremos discutir sobre a implementação dos padrões Observer e Factory em um cenário de gerenciamento de pedidos em uma loja virtual.

Design Patterns com JS

O padrão Observer é um padrão de projeto comportamental que define uma relação de um-para-muitos entre objetos, onde uma mudança no estado de um objeto notifica e atualiza automaticamente todos os seus dependentes. Nesse padrão, os observadores são objetos que se registram para receber notificações quando ocorrem mudanças no estado do objeto observável.

Um exemplo de onde o padrão Observer pode ser aplicado é em sistemas que precisam monitorar eventos em tempo real, como um sistema de notificações de atualizações em uma rede social ou um sistema de monitoramento de tráfego em uma cidade.

O padrão Factory é um padrão de projeto criacional que fornece uma interface para criar objetos em uma superclasse, mas permite que as subclasses alterem o tipo de objetos que serão criados. Isso permite criar objetos sem expor a lógica de criação para o cliente e, ao mesmo tempo, fornecer uma maneira flexível e extensível de criar objetos.

Um exemplo de onde o padrão Factory pode ser aplicado é em um sistema de gerenciamento de pedidos em uma loja virtual, onde diferentes tipos de pedidos podem ser criados, como pedidos de produtos físicos ou pedidos de produtos digitais. O padrão Factory permite que diferentes tipos de pedidos sejam criados usando uma interface comum, sem que o cliente precise saber a lógica de criação por trás de cada tipo de pedido.

Em resumo, o padrão Observer é útil para monitorar eventos em tempo real e atualizar objetos dependentes automaticamente, enquanto o padrão Factory é útil para criar objetos de diferentes tipos de forma flexível e extensível.

Agora vamos direto ao ponto, no entrypoint da aplicação:

const { NotificationService, EmailService } = require("./classServices");
const { Order } = require("./orderClass");
const { OrderStatus } = require("./shared");


const emailService = new EmailService()
const notificationService = new NotificationService(emailService);

const orderId = '123'
const order = new Order(orderId, OrderStatus.PENDING);

order.addObserver(notificationService);

order.setStatus(OrderStatus.DELIVERED)

Esse código está criando uma ordem de compra (Order) com um ID e um status inicial de "PENDING". Em seguida, ele cria uma instância de EmailService e outra instância de NotificationService que recebe a instância de EmailService como parâmetro.

Então, ele adiciona notificationService como um observador da instância de Order criada anteriormente, usando o método addObserver.

Em seguida, ele atualiza o status da ordem de compra para “DELIVERED”, usando o método setStatus. Como notificationService foi adicionado como observador, ele será notificado da mudança de status e poderá tomar a ação apropriada, que, no caso, é enviar um email para o usuário informando sobre a atualização do status.

Em resumo, esse código demonstra o uso do padrão Observer para monitorar mudanças de estado em uma ordem de compra e o padrão Factory para criar instâncias de serviços de notificação (no caso, NotificationService e EmailService).

Agora vamos a implementação das classes NotificationService e EmailService :

class NotificationService {
constructor(emailService) {
this.emailService = emailService;
}

notify(order) {
const orderId = order.id;
const status = order.status;
this.emailService.sendEmail(orderId, `Your order status has been updated to ${status}.`);
}
}

class EmailService {
sendEmail(orderId, message) {
console.log(`Sending email to orderId-${orderId}. With message: "${message}".`)
}
}

module.exports = {
NotificationService,
EmailService,
}

A classe EmailService tem um único método sendEmail que recebe o ID do pedido e uma mensagem como argumentos, e simplesmente exibe uma mensagem no console, indicando que um e-mail está sendo enviado para o ID do pedido com a mensagem especificada.

A classe NotificationService tem um construtor que recebe uma instância da classe EmailService. A classe tem um método notify que é chamado sempre que o status de um pedido é atualizado. O método extrai o ID e o status do pedido e passa para o método sendEmail da instância EmailService fornecida no construtor. Dessa forma, o NotificationService usa a instância do EmailService para enviar e-mails com informações atualizadas sobre o status dos pedidos.

class Order {
constructor(id, status) {
this.id = id;
this.status = status;
this.observers = [];
}

addObserver(observer) {
this.observers.push(observer);
}

removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}

setStatus(status) {
this.status = status;
this.notifyObservers();
}

notifyObservers() {
for (const observer of this.observers) {
observer.notify(this);
}
}
}

module.exports = {
Order,
}

Esse código define uma classe Order que representa um pedido e inclui as seguintes funcionalidades:

  • Atributos: A classe possui um atributo id e um atributo status que representa o status atual do pedido.
  • Observadores: A classe tem a capacidade de adicionar e remover observadores que são notificados quando o status do pedido é alterado.
  • Método setStatus(): Este método permite atualizar o status do pedido. Quando o status é atualizado, ele chama o método notifyObservers() para notificar todos os observadores registrados.
  • Método notifyObservers(): Este método percorre todos os observadores registrados e chama o método notify() em cada um deles, passando o objeto Order como argumento.

Pensando Funcional

Até agora apresentei uma abordagem orientada a objetos para construir os serviços e pedido. Porém, quem me conhece sabe que eu sou um amante e entusiasta em programação funcional. Que tal utilizarmos o padrão Factory para fabricarmos nossos objetos, sem ter que criar essas classes de maneira desnecessária?

Vamos ao NotificationService e EmailService :

function createNotificationService() {
const notificationChannels = [];

function addNotificationChannel(notificationChannel) {
notificationChannels.push(notificationChannel);
}

async function notify(order) {
const { id, status } = order;
const notificationPromises = notificationChannels.map(channel => {
return channel.sendNotification(id, `Your order status has been updated to ${status}.`);
});

await Promise.all(notificationPromises);
}

return {
notify,
addNotificationChannel,
}
}

function createEmailService() {
function sendEmail(orderId, message) {
console.log(`Sending email to orderId-${orderId}. With message: "${message}".`)
}

return {
sendNotification: sendEmail
}
}

module.exports = {
createEmailService,
createNotificationService,
}

E por fim, o próprio pedido:

function createOrder(id, status) {
let observers = [];

function addObserver(observer) {
observers.push(observer);
}

function removeObserver(observer) {
const index = observers.indexOf(observer);
if (index !== -1) {
observers.splice(index, 1);
}
}

function setStatus(status) {
this.status = status;
notifyObservers.call(this);
}

function notifyObservers() {
for (const observer of observers) {
observer.notify(this);
}
}

return {
id,
status,
setStatus,
addObserver,
removeObserver,
};
}

module.exports = {
createOrder,
}

Agora é apenas necessário alterar a criação desses objetos no entrypoint inicial:

const { createEmailService, createNotificationService } = require("./services");
const { createOrder } = require("./order");
const { OrderStatus } = require("./shared");


const emailService = createEmailService()
const notificationService = createNotificationService();
notificationService.addNotificationChannel(emailService)

const orderId = '123'
const order = createOrder(orderId, OrderStatus.PENDING);

order.addObserver(notificationService);

order.setStatus(OrderStatus.DELIVERED)

Conclusão

Em resumo, o Observer Pattern e o Factory Pattern são dois padrões de design muito úteis na programação orientada a objetos, mas não limitante a ela. Enquanto o Observer Pattern permite que objetos sejam notificados de mudanças em outros objetos, o Factory Pattern fornece uma maneira conveniente de criar objetos sem expor a lógica de criação. Além disso, substituir classes por funções usando o Factory Pattern pode trazer vantagens como maior flexibilidade, menor acoplamento e maior simplicidade. Ao entender e aplicar esses padrões em seus projetos de software, podemos melhorar a modularidade, a manutenibilidade e a escalabilidade de sistemas.

Dúvidas, sugestões, críticas, reclamações, anseios e aflições, sinta-se a vontade para entrar em contato comigo.

--

--