Observer e Factory Patterns em Javascript
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.
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 atributostatus
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étodonotifyObservers()
para notificar todos os observadores registrados. - Método
notifyObservers()
: Este método percorre todos os observadores registrados e chama o métodonotify()
em cada um deles, passando o objetoOrder
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.