JavaScript Avançado

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

7 min de leitura

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

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

<h2>Observer: Reatividade em Tempo Real</h2>

<p>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.</p>

<pre><code class="language-javascript">class Subject {

constructor() {

this.observers = [];

}

subscribe(observer) {

this.observers.push(observer);

}

unsubscribe(observer) {

this.observers = this.observers.filter(obs =&gt; obs !== observer);

}

notify(data) {

this.observers.forEach(observer =&gt; 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(&#039;João&#039;);

const user2 = new Observer(&#039;Maria&#039;);

weather.subscribe(user1);

weather.subscribe(user2);

weather.notify(&#039;Chuva esperada&#039;); // Ambos são notificados</code></pre>

<h3>Aplicação Prática: Store de Estado</h3>

<p>Em aplicações modernas, o Observer é usado em gerenciadores de estado. Quando dados mudam, componentes registrados são automaticamente notificados:</p>

<pre><code class="language-javascript">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(&#039;Component1&#039;);

const component2 = new Observer(&#039;Component2&#039;);

appStore.subscribe(component1);

appStore.subscribe(component2);

appStore.setState({ count: 1 });

// Component1 recebeu: { count: 1 }

// Component2 recebeu: { count: 1 }</code></pre>

<h2>Mediator: Comunicação Centralizada</h2>

<p>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.</p>

<pre><code class="language-javascript">class Mediator {

constructor() {

this.colleagues = [];

}

register(colleague) {

this.colleagues.push(colleague);

colleague.setMediator(this);

}

send(message, sender) {

this.colleagues.forEach(colleague =&gt; {

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(&#039;Alice&#039;);

const bob = new Colleague(&#039;Bob&#039;);

chat.register(alice);

chat.register(bob);

alice.send(&#039;Olá Bob!&#039;);

// Alice envia: Olá Bob!

// Bob recebe: Olá Bob!</code></pre>

<h3>Caso Real: Formulário com Validação Interdependente</h3>

<p>Imagine um formulário onde campos dependem uns dos outros. Um mediator simplifica essa lógica:</p>

<pre><code class="language-javascript">class FormMediator {

constructor() {

this.fields = {};

}

registerField(name, field) {

this.fields[name] = field;

field.setMediator(this);

}

validateField(fieldName, value) {

if (fieldName === &#039;password&#039; &amp;&amp; value.length &lt; 8) {

this.fields[&#039;confirmPassword&#039;].disable(&#039;Senha muito curta&#039;);

return false;

}

this.fields[&#039;confirmPassword&#039;].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(&#039;password&#039;);

const confirm = new FormField(&#039;confirmPassword&#039;);

form.registerField(&#039;password&#039;, pwd);

form.registerField(&#039;confirmPassword&#039;, confirm);

pwd.onChange(&#039;abc&#039;); // confirmPassword desabilitado

pwd.onChange(&#039;password123&#039;); // confirmPassword habilitado</code></pre>

<h2>Command: Ações Desacopladas e Reversíveis</h2>

<p>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.</p>

<pre><code class="language-javascript">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(&#039;Luz acesa&#039;);

}

off() {

console.log(&#039;Luz apagada&#039;);

}

}

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</code></pre>

<h3>Editor com Undo/Redo Completo</h3>

<p>Um exemplo mais realista mostra como integrar múltiplos comandos em um editor de texto:</p>

<pre><code class="language-javascript">class Document {

constructor() {

this.content = &#039;&#039;;

}

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 &gt; 0) {

const command = this.history.pop();

command.undo();

this.undone.push(command);

}

}

redo() {

if (this.undone.length &gt; 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(&#039;Olá&#039;);

editor.type(&#039; Mundo&#039;);

console.log(editor.getContent()); // Olá Mundo

editor.undo();

console.log(editor.getContent()); // Olá

editor.redo();

console.log(editor.getContent()); // Olá Mundo</code></pre>

<h2>Conclusão</h2>

<p>Observer, Mediator e Command resolvem problemas distintos mas complementares: <strong>Observer</strong> cria reatividade automática através de notificações; <strong>Mediator</strong> centraliza comunicação complexa reduzindo acoplamento; <strong>Command</strong> 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.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://refactoring.guru/design-patterns/observer" target="_blank" rel="noopener noreferrer">Observer Pattern - Refactoring Guru</a></li>

<li><a href="https://refactoring.guru/design-patterns/mediator" target="_blank" rel="noopener noreferrer">Mediator Pattern - Refactoring Guru</a></li>

<li><a href="https://refactoring.guru/design-patterns/command" target="_blank" rel="noopener noreferrer">Command Pattern - Refactoring Guru</a></li>

<li><a href="https://developer.mozilla.org/en-US/docs/Learn" target="_blank" rel="noopener noreferrer">Design Patterns in JavaScript - MDN Web Docs</a></li>

<li><a href="https://www.patterns.dev/posts/classic-design-patterns/" target="_blank" rel="noopener noreferrer">Learning JavaScript Design Patterns - Addy Osmani</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript
O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript

Template Literal Types em TypeScript Template Literal Types permitem criar ti...

Design Patterns em JavaScript: Strategy, Decorator e Composite: Do Básico ao Avançado
Design Patterns em JavaScript: Strategy, Decorator e Composite: Do Básico ao Avançado

Strategy Pattern: Flexibilidade no Comportamento O Strategy Pattern encapsula...

Streams Avançados em Node.js: Transform, Duplex e Backpressure na Prática
Streams Avançados em Node.js: Transform, Duplex e Backpressure na Prática

Streams Avançados em Node.js: Transform, Duplex e Backpressure Streams são um...