<h2>Strategy Pattern: Flexibilidade no Comportamento</h2>
<p>O Strategy Pattern encapsula diferentes algoritmos em classes separadas, permitindo que o cliente escolha qual usar em tempo de execução. É especialmente útil quando você tem múltiplas formas de resolver o mesmo problema e quer evitar condicionais espalhados pelo código.</p>
<p>Imagine um sistema de processamento de pagamentos. Em vez de usar um <code>if/else</code> gigante, criamos estratégias independentes:</p>
<pre><code class="language-javascript">// Estratégias de pagamento
class PaymentStrategy {
pay(amount) {}
}
class CreditCardStrategy extends PaymentStrategy {
constructor(cardNumber, cvv) {
super();
this.cardNumber = cardNumber;
this.cvv = cvv;
}
pay(amount) {
console.log(Pagando $${amount} com cartão ${this.cardNumber});
return true;
}
}
class PayPalStrategy extends PaymentStrategy {
constructor(email) {
super();
this.email = email;
}
pay(amount) {
console.log(Pagando $${amount} via PayPal (${this.email}));
return true;
}
}
class CryptoCurrencyStrategy extends PaymentStrategy {
constructor(walletAddress) {
super();
this.walletAddress = walletAddress;
}
pay(amount) {
console.log(Pagando ${amount} BTC para ${this.walletAddress});
return true;
}
}
// Contexto
class PaymentProcessor {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
processPayment(amount) {
return this.strategy.pay(amount);
}
}
// Uso
const processor = new PaymentProcessor(
new CreditCardStrategy('1234-5678-9012-3456', '123')
);
processor.processPayment(100); // Pagando $100 com cartão...
processor.setStrategy(new PayPalStrategy('user@example.com'));
processor.processPayment(50); // Pagando $50 via PayPal...</code></pre>
<p>O benefício real é a manutenibilidade: adicionar novo método de pagamento não requer modificar <code>PaymentProcessor</code>, apenas criar uma nova estratégia. Isso respeita o princípio Open/Closed do SOLID.</p>
<h2>Decorator Pattern: Adicionando Funcionalidades Dinamicamente</h2>
<p>O Decorator permite adicionar responsabilidades a um objeto dinamicamente, sem usar herança. É como envolver presentes: cada camada de papel adicionada é um decorator que mantém a funcionalidade anterior e acrescenta a sua.</p>
<p>Considere um sistema de cafeteria onde você constrói bebidas com adições:</p>
<pre><code class="language-javascript">// Componente base
class Coffee {
cost() {
return 5;
}
description() {
return 'Café preto';
}
}
// Decoradores
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() {
return this.coffee.cost();
}
description() {
return this.coffee.description();
}
}
class MilkDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 2;
}
description() {
return this.coffee.description() + ', com leite';
}
}
class CaramelDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 1.5;
}
description() {
return this.coffee.description() + ', com calda de caramelo';
}
}
class WhippedCreamDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 1;
}
description() {
return this.coffee.description() + ', com chantilly';
}
}
// Uso
let coffee = new Coffee();
console.log(${coffee.description()} - $${coffee.cost()});
// Café preto - $5
coffee = new MilkDecorator(coffee);
console.log(${coffee.description()} - $${coffee.cost()});
// Café preto, com leite - $7
coffee = new CaramelDecorator(coffee);
console.log(${coffee.description()} - $${coffee.cost()});
// Café preto, com leite, com calda de caramelo - $8.5
coffee = new WhippedCreamDecorator(coffee);
console.log(${coffee.description()} - $${coffee.cost()});
// Café preto, com leite, com calda de caramelo, com chantilly - $9.5</code></pre>
<p>A vantagem é clara: você compõe funcionalidades em tempo de execução sem criar classes explosivas como <code>CoffeeWithMilkAndCaramelAndWhippedCream</code>. Cada decorator é independente e reutilizável.</p>
<h2>Composite Pattern: Estruturas Hierárquicas Simplificadas</h2>
<p>O Composite permite compor objetos em estruturas de árvore, tratando objetos individuais e composições uniformemente. Perfeito para menus, estruturas de arquivos ou qualquer hierarquia.</p>
<p>Um exemplo prático é um menu de aplicação com submenus:</p>
<pre><code class="language-javascript">// Interface comum
class MenuItem {
execute() {}
}
// Leaf (folha)
class Command extends MenuItem {
constructor(name, action) {
super();
this.name = name;
this.action = action;
}
execute() {
console.log(Executando: ${this.name});
this.action();
}
}
// Composite (ramo)
class Menu extends MenuItem {
constructor(name) {
super();
this.name = name;
this.items = [];
}
add(item) {
this.items.push(item);
return this;
}
remove(item) {
this.items = this.items.filter(i => i !== item);
return this;
}
execute() {
console.log(\n=== ${this.name} ===);
this.items.forEach(item => item.execute());
}
}
// Uso
const mainMenu = new Menu('Menu Principal');
const fileMenu = new Menu('Arquivo');
fileMenu
.add(new Command('Novo', () => console.log(' Criando novo documento...')))
.add(new Command('Abrir', () => console.log(' Abrindo arquivo...')))
.add(new Command('Salvar', () => console.log(' Salvando...')));
const editMenu = new Menu('Editar');
editMenu
.add(new Command('Copiar', () => console.log(' Copiando...')))
.add(new Command('Colar', () => console.log(' Colando...')));
mainMenu.add(fileMenu).add(editMenu);
mainMenu.execute();
// === Menu Principal ===
// === Arquivo ===
// Criando novo documento...
// Abrindo arquivo...
// Salvando...
// === Editar ===
// Copiando...
// Colando...</code></pre>
<p>O ganho é elegante: tratamos <code>Menu</code> e <code>Command</code> pela mesma interface. Adicionar novos comandos ou submenus não altera o código existente. A recursão natural da árvore torna tudo simplista.</p>
<h2>Conclusão</h2>
<p>Dominei três patterns fundamentais que transformam seu código:</p>
<ol>
<li><strong>Strategy</strong> elimina condicionais para seleção de algoritmos, tornando o código extensível sem modificação.</li>
</ol>
<ol>
<li><strong>Decorator</strong> substitui herança profunda por composição, adicionando funcionalidades de forma granular e reutilizável.</li>
</ol>
<ol>
<li><strong>Composite</strong> simplifica trabalho com estruturas hierárquicas, tratando partes e todo de forma uniforme.</li>
</ol>
<p>Esses padrões não são fins em si mesmos—são ferramentas. Use-os quando o problema os justificar, não por usar design patterns.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://refactoring.guru/design-patterns/book" target="_blank" rel="noopener noreferrer">Design Patterns: Elements of Reusable Object-Oriented Software</a> — Gamma, Helm, Johnson, Vlissides</li>
<li><a href="https://refactoring.guru/design-patterns/javascript" target="_blank" rel="noopener noreferrer">Refactoring Guru - Design Patterns JavaScript</a></li>
<li><a href="https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Classes" target="_blank" rel="noopener noreferrer">MDN Web Docs - Classes JavaScript</a></li>
<li><a href="https://github.com/getify/You-Dont-Know-JS" target="_blank" rel="noopener noreferrer">You Don't Know JS: Object Prototypes</a></li>
<li><a href="https://www.patterns.dev/posts/classic-design-patterns/" target="_blank" rel="noopener noreferrer">Pattern for JavaScript: Addy Osmani</a></li>
</ul>