O que são Design Patterns?
Design Patterns são soluções comprovadas para problemas comuns em desenvolvimento de software. Eles não são código pronto para copiar, mas sim diretrizes que orientam a estrutura e organização do seu projeto. Em JavaScript, os padrões são especialmente valiosos porque a linguagem oferece flexibilidade — talvez até demais. Dominar Factory, Singleton e Builder significa ter ferramentas para criar arquiteturas escaláveis, testáveis e maintíveis.
Estes três padrões pertencem à categoria de padrões criacionais, ou seja, lidam com a forma como os objetos são instanciados. Entender quando e como usá-los é essencial para qualquer desenvolvedor que queira evoluir além de código procedural.
Factory Pattern
Conceito e Aplicação
O Factory Pattern encapsula a lógica de criação de objetos em uma função ou classe dedicada. Em vez de espalharem new por toda a aplicação, você centraliza como os objetos nascem. Isso torna mudanças futuras mais fáceis e desacopla o código que usa o objeto de sua implementação concreta.
Imagine uma aplicação com diferentes tipos de usuários: Admin, Guest e Premium. Sem Factory, seu código precisaria saber dos detalhes de cada classe. Com Factory, você delega isso:
// Sem factory (problemático)
let user;
if (type === 'admin') {
user = new Admin(name);
} else if (type === 'guest') {
user = new Guest(name);
}
// Com factory (profissional)
class UserFactory {
static create(type, name) {
switch(type) {
case 'admin':
return new Admin(name, true);
case 'premium':
return new Premium(name, true);
case 'guest':
return new Guest(name, false);
default:
throw new Error(`Tipo ${type} desconhecido`);
}
}
}
class Admin {
constructor(name, isAdmin) {
this.name = name;
this.isAdmin = isAdmin;
this.permissions = ['read', 'write', 'delete'];
}
}
class Guest {
constructor(name, isAdmin) {
this.name = name;
this.isAdmin = isAdmin;
this.permissions = ['read'];
}
}
// Uso
const admin = UserFactory.create('admin', 'João');
const guest = UserFactory.create('guest', 'Maria');
O benefício é claro: se precisar adicionar um novo tipo de usuário, você só modifica a Factory. Toda a aplicação continua funcionando sem mudanças.
Singleton Pattern
Evitando Múltiplas Instâncias
Singleton garante que uma classe tenha apenas uma instância em toda a aplicação e fornece um ponto global de acesso a ela. É perfeito para objetos que representam recursos únicos: configurações, loggers, conexões de banco de dados.
O desafio em JavaScript é implementar isso de forma segura, impedindo que alguém acidentalmente crie uma nova instância:
class Database {
constructor(connectionString) {
// Previne múltiplas instâncias
if (Database.instance) {
return Database.instance;
}
this.connectionString = connectionString;
this.connected = false;
Database.instance = this;
}
connect() {
if (!this.connected) {
console.log(`Conectando a ${this.connectionString}`);
this.connected = true;
}
}
query(sql) {
if (!this.connected) {
throw new Error('Banco não conectado');
}
return `Executando: ${sql}`;
}
}
// Uso
const db1 = new Database('localhost:5432');
const db2 = new Database('other-host:5432');
console.log(db1 === db2); // true — mesma instância!
db1.connect();
console.log(db2.query('SELECT * FROM users')); // Funciona
Uma alternativa moderna e elegante é usar um closure:
const Logger = (() => {
let instance;
return {
getInstance() {
if (!instance) {
instance = {
logs: [],
log(message) {
this.logs.push(message);
console.log(message);
}
};
}
return instance;
}
};
})();
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
logger1.log('Erro crítico');
console.log(logger1 === logger2); // true
Use Singleton com moderação — dependências globais podem complicar testes unitários. Prefira injeção de dependência quando possível.
Builder Pattern
Construindo Objetos Complexos Passo a Passo
O Builder Pattern é ideal quando você precisa criar objetos com muitos parâmetros opcionais ou configurações complexas. Em vez de um construtor gigante, você encadeia métodos de configuração, tornando o código legível e flexível.
Considere montar uma requisição HTTP com várias opções:
class RequestBuilder {
constructor(url) {
this.url = url;
this.method = 'GET';
this.headers = {};
this.body = null;
this.timeout = 5000;
}
setMethod(method) {
this.method = method;
return this; // Retorna this para encadeamento
}
addHeader(key, value) {
this.headers[key] = value;
return this;
}
setBody(body) {
this.body = body;
return this;
}
setTimeout(ms) {
this.timeout = ms;
return this;
}
build() {
return {
url: this.url,
method: this.method,
headers: this.headers,
body: this.body,
timeout: this.timeout
};
}
}
// Uso fluido e intuitivo
const request = new RequestBuilder('https://api.example.com/users')
.setMethod('POST')
.addHeader('Content-Type', 'application/json')
.addHeader('Authorization', 'Bearer token123')
.setBody({ name: 'João', email: 'joao@example.com' })
.setTimeout(10000)
.build();
console.log(request);
O padrão melhora enormemente a legibilidade. Compare: new RequestBuilder('url').setMethod('POST').addHeader(...) versus um construtor com 8 parâmetros posicionais onde você esqueceria qual vem primeiro.
Quando Usar Cada Padrão
Factory: Use quando você precisa criar objetos de diferentes tipos baseado em condições. Exemplos reais: loaders de arquivo (ImageFactory, VideoFactory), criadores de widgets UI, geradores de reportes.
Singleton: Use para recursos únicos que devem ser acessados globalmente. Exemplos: logger único, pool de conexões, configurações da aplicação, cache central.
Builder: Use quando o objeto tem muitos parâmetros opcionais ou quando a construção é complexa. Exemplos: configuradores de aplicação, construtores de queries SQL, builders de componentes UI.
Conclusão
Design Patterns não são sobre memorizar nomes — são sobre reconhecer problemas e aplicar soluções comprovadas. Factory resolve o problema de criação variável, encapsulando lógica condicional. Singleton garante unicidade, útil para recursos compartilhados. Builder torna a construção legível e flexível, especialmente com muitas opções.
A chave é moderação: use padrões quando agregam valor real, não por usar. Um Singleton desnecessário complica testes. Uma Factory trivial é overhead. Código simples que funciona vence padrões mal aplicados.
Continue praticando, estude padrões estruturais (Decorator, Adapter) e comportamentais (Observer, Strategy) depois. A jornada é gradual, mas cada padrão dominado torna você um desenvolvedor mais capaz.