Boas Práticas de Design Patterns em JavaScript: Observer, Mediator e Command para Times Ágeis Já leu

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. ${this.name} recebeu: ${data} 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: 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. ${this.name} envia: ${message} ${this.name} recebe: ${message} Caso Real: Formulário com Validação Interdependente Imagine um formulário onde campos dependem uns dos outros. Um mediator simplifica essa lógica: Conclusão Observer, Mediator e Command resolvem

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.

Referências


Artigos relacionados