Observer: Reatividade em Tempo Real
O padrão Observer implementa um sistema de publicação-subscrição onde objetos (observers) se registram para receber notificações quando um assunto (subject) sofre mudanças. É fundamental em aplicações reativas, formulários dinâmicos e eventos globais. A ideia é desacoplar produtor e consumidor de dados, permitindo que múltiplos observadores reajam simultaneamente.
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} recebeu: ${data}`);
}
}
const weather = new Subject();
const user1 = new Observer('João');
const user2 = new Observer('Maria');
weather.subscribe(user1);
weather.subscribe(user2);
weather.notify('Chuva esperada'); // Ambos são notificados
Aplicação Prática: Store de Estado
Em aplicações modernas, o Observer é usado em gerenciadores de estado. Quando dados mudam, componentes registrados são automaticamente notificados:
class Store extends Subject {
constructor(initialState = {}) {
super();
this.state = initialState;
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.notify(this.state);
}
getState() {
return this.state;
}
}
const appStore = new Store({ count: 0 });
const component1 = new Observer('Component1');
const component2 = new Observer('Component2');
appStore.subscribe(component1);
appStore.subscribe(component2);
appStore.setState({ count: 1 });
// Component1 recebeu: { count: 1 }
// Component2 recebeu: { count: 1 }
Mediator: Comunicação Centralizada
O padrão Mediator reduz o acoplamento entre objetos ao centralizar a lógica de comunicação em um intermediário. Em vez de componentes falarem diretamente uns com os outros, todos conversam através do mediator. É excelente para dashboards complexos, diálogos modais com múltiplos campos e sistemas de chat.
class Mediator {
constructor() {
this.colleagues = [];
}
register(colleague) {
this.colleagues.push(colleague);
colleague.setMediator(this);
}
send(message, sender) {
this.colleagues.forEach(colleague => {
if (colleague !== sender) {
colleague.receive(message);
}
});
}
}
class Colleague {
constructor(name) {
this.name = name;
this.mediator = null;
}
setMediator(mediator) {
this.mediator = mediator;
}
send(message) {
console.log(`${this.name} envia: ${message}`);
this.mediator.send(message, this);
}
receive(message) {
console.log(`${this.name} recebe: ${message}`);
}
}
const chat = new Mediator();
const alice = new Colleague('Alice');
const bob = new Colleague('Bob');
chat.register(alice);
chat.register(bob);
alice.send('Olá Bob!');
// Alice envia: Olá Bob!
// Bob recebe: Olá Bob!
Caso Real: Formulário com Validação Interdependente
Imagine um formulário onde campos dependem uns dos outros. Um mediator simplifica essa lógica:
class FormMediator {
constructor() {
this.fields = {};
}
registerField(name, field) {
this.fields[name] = field;
field.setMediator(this);
}
validateField(fieldName, value) {
if (fieldName === 'password' && value.length < 8) {
this.fields['confirmPassword'].disable('Senha muito curta');
return false;
}
this.fields['confirmPassword'].enable();
return true;
}
}
class FormField {
constructor(name) {
this.name = name;
this.mediator = null;
this.enabled = true;
}
setMediator(mediator) {
this.mediator = mediator;
}
onChange(value) {
if (this.mediator.validateField(this.name, value)) {
console.log(`${this.name}: Válido`);
}
}
disable(reason) {
this.enabled = false;
console.log(`${this.name} desabilitado: ${reason}`);
}
enable() {
this.enabled = true;
console.log(`${this.name} habilitado`);
}
}
const form = new FormMediator();
const pwd = new FormField('password');
const confirm = new FormField('confirmPassword');
form.registerField('password', pwd);
form.registerField('confirmPassword', confirm);
pwd.onChange('abc'); // confirmPassword desabilitado
pwd.onChange('password123'); // confirmPassword habilitado
Command: Ações Desacopladas e Reversíveis
O padrão Command encapsula uma solicitação como um objeto, permitindo parametrizar clientes com diferentes requisições, enfileirar operações, e implementar desfazer/refazer. É perfeito para undo/redo, filas de tarefas, macros e botões configuráveis.
class Command {
execute() {}
undo() {}
}
class LightCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.on();
}
undo() {
this.light.off();
}
}
class Light {
on() {
console.log('Luz acesa');
}
off() {
console.log('Luz apagada');
}
}
class Invoker {
constructor() {
this.history = [];
}
executeCommand(command) {
command.execute();
this.history.push(command);
}
undo() {
const command = this.history.pop();
if (command) command.undo();
}
}
const light = new Light();
const switchLight = new LightCommand(light);
const remote = new Invoker();
remote.executeCommand(switchLight); // Luz acesa
remote.undo(); // Luz apagada
Editor com Undo/Redo Completo
Um exemplo mais realista mostra como integrar múltiplos comandos em um editor de texto:
class Document {
constructor() {
this.content = '';
}
insertText(text) {
this.content += text;
}
deleteText(length) {
this.content = this.content.slice(0, -length);
}
getText() {
return this.content;
}
}
class InsertCommand extends Command {
constructor(document, text) {
super();
this.document = document;
this.text = text;
}
execute() {
this.document.insertText(this.text);
}
undo() {
this.document.deleteText(this.text.length);
}
}
class Editor {
constructor(document) {
this.document = document;
this.history = [];
this.undone = [];
}
type(text) {
const command = new InsertCommand(this.document, text);
command.execute();
this.history.push(command);
this.undone = [];
}
undo() {
if (this.history.length > 0) {
const command = this.history.pop();
command.undo();
this.undone.push(command);
}
}
redo() {
if (this.undone.length > 0) {
const command = this.undone.pop();
command.execute();
this.history.push(command);
}
}
getContent() {
return this.document.getText();
}
}
const doc = new Document();
const editor = new Editor(doc);
editor.type('Olá');
editor.type(' Mundo');
console.log(editor.getContent()); // Olá Mundo
editor.undo();
console.log(editor.getContent()); // Olá
editor.redo();
console.log(editor.getContent()); // Olá Mundo
Conclusão
Observer, Mediator e Command resolvem problemas distintos mas complementares: Observer cria reatividade automática através de notificações; Mediator centraliza comunicação complexa reduzindo acoplamento; Command encapsula ações permitindo composição, fila e desfazer. Esses padrões são a base de frameworks modernos—conhecê-los profundamente torna você capaz de arquitetar aplicações escaláveis e manuteníveis. Escolha o padrão certo para cada problema: não use Observer para lógica de negócio centralizada, nem Mediator quando simples callbacks bastam.