Padrões de Projeto em JavaScript com Exemplos
Padrões de projeto (design patterns) são soluções comprovadas para problemas típicos no desenvolvimento de software. Para um desenvolvedor JavaScript, conhecer padrões é especialmente importante, pois a linguagem possui um modelo de objeto flexível e é amplamente usada tanto no cliente quanto no servidor (Node.js). Neste artigo, vamos analisar quatro padrões-chave com exemplos práticos em JavaScript.
Por que estudar padrões de projeto?
Os padrões ajudam a:
- Simplificar a manutenção e escalabilidade do código
- Tornar o código mais legível e previsível
- Evitar "reinventar a roda"
- Melhorar a comunicação na equipe (linguagem comum)
1. Singleton (Instância Única)
O padrão Singleton garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. No Node.js, esse padrão é frequentemente usado para conectar-se a um banco de dados, trabalhar com configuração ou logging.
class DatabaseConnection { constructor() { if (DatabaseConnection.instance) { return DatabaseConnection.instance; } this.connection = null; DatabaseConnection.instance = this; }
connect() { // Simulação de conexão com o BD this.connection = "connected"; console.log("Conexão com o BD estabelecida"); }
query(sql) { if (!this.connection) { throw new Error("Sem conexão com o BD"); } console.log(`Executando consulta: ${sql}`); return { data: [] }; }}
// Usoconst db1 = new DatabaseConnection();const db2 = new DatabaseConnection();
db1.connect();console.log(db1 === db2); // true - ambos apontam para a mesma instânciadb2.query("SELECT * FROM users");Quando usar: gerenciamento de recursos compartilhados (BD, configuração, cache). Cuidado: o uso excessivo pode levar a um forte acoplamento do código.
2. Factory (Fábrica)
O Factory Method define uma interface para criar um objeto, mas permite que subclasses alterem o tipo do objeto criado. Em JavaScript, isso é frequentemente implementado por meio de funções ou classes comuns.
class User { constructor(name, role) { this.name = name; this.role = role; }
getPermissions() { return []; }}
class Admin extends User { getPermissions() { return ["read", "write", "delete", "manage"]; }}
class Editor extends User { getPermissions() { return ["read", "write"]; }}
class Viewer extends User { getPermissions() { return ["read"]; }}
// Fábricaclass UserFactory { createUser(name, role) { switch (role) { case "admin": return new Admin(name, role); case "editor": return new Editor(name, role); case "viewer": return new Viewer(name, role); default: throw new Error("Função desconhecida"); } }}
// Usoconst factory = new UserFactory();const admin = factory.createUser("Ana", "admin");const viewer = factory.createUser("João", "viewer");
console.log(admin.getPermissions()); // ["read", "write", "delete", "manage"]console.log(viewer.getPermissions()); // ["read"]Vantagens: gerenciamento centralizado da criação de objetos, facilidade para adicionar novos tipos, ocultação da lógica de instanciação.
3. Observer (Observador)
O padrão Observer define uma dependência "um para muitos" entre objetos. Quando um objeto (sujeito) muda de estado, todos os objetos dependentes (observadores) são notificados automaticamente. Esta é a base da programação orientada a eventos no Node.js.
class EventEmitter { constructor() { this.events = {}; }
on(eventName, listener) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[even
```