Design Patterns sao um catalogo de solucoes reutilizaveis para problemas de design de software. Passados 30 anos desde o livro do GoF (Gang of Four) de 1994, embora a essencia dos padroes permaneca a mesma, suas formas de aplicacao evoluiram significativamente. Neste artigo, explicamos como aplicar padroes de forma pratica em ambientes de desenvolvimento modernos.
Classificacao dos Design Patterns
flowchart TB
subgraph DP["Design Patterns"]
subgraph Creational["Padroes Criacionais"]
C1["Singleton"]
C2["Factory Method"]
C3["Abstract Factory"]
C4["Builder"]
C5["Prototype"]
end
subgraph Structural["Padroes Estruturais"]
S1["Adapter"]
S2["Bridge"]
S3["Composite"]
S4["Decorator"]
S5["Facade"]
S6["Flyweight"]
S7["Proxy"]
end
subgraph Behavioral["Padroes Comportamentais"]
B1["Strategy"]
B2["Observer"]
B3["Command"]
B4["State"]
B5["Template Method"]
B6["Iterator"]
B7["Mediator"]
B8["Memento"]
B9["Visitor"]
B10["Chain of Resp."]
end
end
Padroes Criacionais
Padrao Factory Method
Delega a criacao de objetos para subclasses, separando a logica de criacao.
// Implementacao de Factory Method em TypeScript moderno
// Interface do produto
interface Notification {
send(message: string): Promise<void>;
}
// Produtos concretos
class EmailNotification implements Notification {
constructor(private email: string) {}
async send(message: string): Promise<void> {
console.log(`Email to ${this.email}: ${message}`);
// Logica real de envio de email
}
}
class SlackNotification implements Notification {
constructor(private webhookUrl: string) {}
async send(message: string): Promise<void> {
console.log(`Slack webhook: ${message}`);
// Chamada da API do Slack
}
}
class SMSNotification implements Notification {
constructor(private phoneNumber: string) {}
async send(message: string): Promise<void> {
console.log(`SMS to ${this.phoneNumber}: ${message}`);
// Chamada da API de envio de SMS
}
}
// Factory (baseada em funcao - abordagem moderna)
type NotificationType = 'email' | 'slack' | 'sms';
interface NotificationConfig {
type: NotificationType;
email?: string;
webhookUrl?: string;
phoneNumber?: string;
}
function createNotification(config: NotificationConfig): Notification {
switch (config.type) {
case 'email':
if (!config.email) throw new Error('Email required');
return new EmailNotification(config.email);
case 'slack':
if (!config.webhookUrl) throw new Error('Webhook URL required');
return new SlackNotification(config.webhookUrl);
case 'sms':
if (!config.phoneNumber) throw new Error('Phone number required');
return new SMSNotification(config.phoneNumber);
default:
throw new Error(`Unknown notification type: ${config.type}`);
}
}
// Exemplo de uso
const notification = createNotification({
type: 'email',
email: 'user@example.com'
});
await notification.send('Hello!');
Padrao Builder
Constroi objetos complexos de forma gradual.
// Padrao Builder - Implementacao com Fluent API
interface HttpRequestConfig {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
url: string;
headers: Record<string, string>;
body?: unknown;
timeout: number;
retries: number;
}
class HttpRequestBuilder {
private config: Partial<HttpRequestConfig> = {
method: 'GET',
headers: {},
timeout: 30000,
retries: 0,
};
url(url: string): this {
this.config.url = url;
return this;
}
method(method: HttpRequestConfig['method']): this {
this.config.method = method;
return this;
}
header(key: string, value: string): this {
this.config.headers = {
...this.config.headers,
[key]: value,
};
return this;
}
authorization(token: string): this {
return this.header('Authorization', `Bearer ${token}`);
}
contentType(type: string): this {
return this.header('Content-Type', type);
}
json(data: unknown): this {
this.config.body = data;
return this.contentType('application/json');
}
timeout(ms: number): this {
this.config.timeout = ms;
return this;
}
retries(count: number): this {
this.config.retries = count;
return this;
}
build(): HttpRequestConfig {
if (!this.config.url) {
throw new Error('URL is required');
}
return this.config as HttpRequestConfig;
}
// Metodo conveniente - execucao direta
async execute<T>(): Promise<T> {
const config = this.build();
// Logica real de execucao do fetch
const response = await fetch(config.url, {
method: config.method,
headers: config.headers,
body: config.body ? JSON.stringify(config.body) : undefined,
});
return response.json();
}
}
// Exemplo de uso
const response = await new HttpRequestBuilder()
.url('https://api.example.com/users')
.method('POST')
.authorization('my-token')
.json({ name: 'John', email: 'john@example.com' })
.timeout(5000)
.retries(3)
.execute<{ id: string }>();
Padrao Singleton (Alternativas Modernas)
Evite o Singleton tradicional e aproveite containers de DI ou escopo de modulo.
// Nao recomendado: Singleton tradicional (evitar)
class LegacySingleton {
private static instance: LegacySingleton;
private constructor() {}
static getInstance(): LegacySingleton {
if (!LegacySingleton.instance) {
LegacySingleton.instance = new LegacySingleton();
}
return LegacySingleton.instance;
}
}
// Recomendado: Singleton no escopo do modulo
// database.ts
class DatabaseConnection {
constructor(private connectionString: string) {}
async query<T>(sql: string): Promise<T[]> {
// Execucao da query
return [];
}
}
// Exportar no nivel do modulo
export const db = new DatabaseConnection(process.env.DATABASE_URL!);
// Recomendado: Usar container de DI
// container.ts
import { Container } from 'inversify';
const container = new Container();
container.bind<DatabaseConnection>('Database')
.to(DatabaseConnection)
.inSingletonScope();
export { container };
Padroes Estruturais
Padrao Adapter
Faz a ponte entre interfaces incompativeis.
// Adaptar API legada para nova interface
// Sistema legado existente
interface LegacyPaymentSystem {
processPayment(
amount: number,
cardNumber: string,
expiry: string,
cvv: string
): boolean;
}
class LegacyStripePayment implements LegacyPaymentSystem {
processPayment(
amount: number,
cardNumber: string,
expiry: string,
cvv: string
): boolean {
console.log('Processing via legacy Stripe...');
return true;
}
}
// Nova interface
interface PaymentGateway {
charge(payment: PaymentDetails): Promise<PaymentResult>;
}
interface PaymentDetails {
amount: number;
currency: string;
card: {
number: string;
expiryMonth: number;
expiryYear: number;
cvc: string;
};
}
interface PaymentResult {
success: boolean;
transactionId: string;
error?: string;
}
// Adapter
class LegacyPaymentAdapter implements PaymentGateway {
constructor(private legacySystem: LegacyPaymentSystem) {}
async charge(payment: PaymentDetails): Promise<PaymentResult> {
const expiry = `${payment.card.expiryMonth}/${payment.card.expiryYear}`;
const success = this.legacySystem.processPayment(
payment.amount,
payment.card.number,
expiry,
payment.card.cvc
);
return {
success,
transactionId: success ? crypto.randomUUID() : '',
error: success ? undefined : 'Payment failed',
};
}
}
// Exemplo de uso
const legacyStripe = new LegacyStripePayment();
const paymentGateway: PaymentGateway = new LegacyPaymentAdapter(legacyStripe);
const result = await paymentGateway.charge({
amount: 1000,
currency: 'BRL',
card: {
number: '4242424242424242',
expiryMonth: 12,
expiryYear: 2025,
cvc: '123',
},
});
Padrao Decorator
Adiciona funcionalidades dinamicamente a objetos existentes.
// Implementacao usando Decorators do TypeScript
// Decorator de metodo - saida de log
function Log(
target: object,
propertyKey: string,
descriptor: PropertyDescriptor
): PropertyDescriptor {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: unknown[]) {
console.log(`[${propertyKey}] Called with:`, args);
const start = performance.now();
try {
const result = await originalMethod.apply(this, args);
const duration = performance.now() - start;
console.log(`[${propertyKey}] Returned:`, result, `(${duration}ms)`);
return result;
} catch (error) {
console.error(`[${propertyKey}] Error:`, error);
throw error;
}
};
return descriptor;
}
// Decorator de cache
function Cache(ttlMs: number = 60000) {
const cache = new Map<string, { value: unknown; expiry: number }>();
return function (
target: object,
propertyKey: string,
descriptor: PropertyDescriptor
): PropertyDescriptor {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: unknown[]) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && cached.expiry > Date.now()) {
console.log(`[Cache Hit] ${propertyKey}`);
return cached.value;
}
const result = await originalMethod.apply(this, args);
cache.set(key, { value: result, expiry: Date.now() + ttlMs });
return result;
};
return descriptor;
};
}
// Decorator de retry
function Retry(maxAttempts: number = 3, delayMs: number = 1000) {
return function (
target: object,
propertyKey: string,
descriptor: PropertyDescriptor
): PropertyDescriptor {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: unknown[]) {
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
lastError = error as Error;
console.warn(`[Retry] Attempt ${attempt}/${maxAttempts} failed`);
if (attempt < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
}
throw lastError!;
};
return descriptor;
};
}
// Aplicacao dos decorators
class UserService {
@Log
@Cache(30000) // Cache de 30 segundos
@Retry(3, 1000)
async getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
}
Padrao Proxy
Controla o acesso a objetos.
// Implementacao usando ES6 Proxy
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
// Proxy de validacao
function createValidatedUser(user: User): User {
return new Proxy(user, {
set(target, property, value) {
if (property === 'email') {
if (typeof value !== 'string' || !value.includes('@')) {
throw new Error('Invalid email format');
}
}
if (property === 'role') {
if (!['admin', 'user'].includes(value)) {
throw new Error('Invalid role');
}
}
return Reflect.set(target, property, value);
},
});
}
// Proxy de lazy loading
function createLazyLoader<T extends object>(
loader: () => Promise<T>
): T {
let instance: T | null = null;
let loading: Promise<T> | null = null;
return new Proxy({} as T, {
get(target, property) {
if (!instance) {
if (!loading) {
loading = loader().then(loaded => {
instance = loaded;
return loaded;
});
}
// Escolha retornar promise ou bloquear
return loading.then(inst => (inst as any)[property]);
}
return (instance as any)[property];
},
});
}
// Proxy de controle de acesso
function createSecureObject<T extends object>(
obj: T,
currentUser: User
): T {
return new Proxy(obj, {
get(target, property) {
const value = Reflect.get(target, property);
// Propriedades restritas a administradores
const adminOnlyProps = ['password', 'secretKey', 'apiToken'];
if (adminOnlyProps.includes(String(property))) {
if (currentUser.role !== 'admin') {
throw new Error('Access denied: Admin only');
}
}
return value;
},
set(target, property, value) {
if (currentUser.role !== 'admin') {
throw new Error('Access denied: Read only');
}
return Reflect.set(target, property, value);
},
});
}
Padroes Comportamentais
Padrao Strategy
Encapsula algoritmos e os torna intercambiaveis.
// Strategy de calculo de precos
interface PricingStrategy {
calculatePrice(basePrice: number, quantity: number): number;
getName(): string;
}
class RegularPricing implements PricingStrategy {
calculatePrice(basePrice: number, quantity: number): number {
return basePrice * quantity;
}
getName(): string {
return 'Regular';
}
}
class BulkPricing implements PricingStrategy {
constructor(private discountThreshold: number, private discountRate: number) {}
calculatePrice(basePrice: number, quantity: number): number {
if (quantity >= this.discountThreshold) {
return basePrice * quantity * (1 - this.discountRate);
}
return basePrice * quantity;
}
getName(): string {
return `Bulk (${this.discountRate * 100}% off for ${this.discountThreshold}+)`;
}
}
class SubscriberPricing implements PricingStrategy {
constructor(private memberDiscountRate: number) {}
calculatePrice(basePrice: number, quantity: number): number {
return basePrice * quantity * (1 - this.memberDiscountRate);
}
getName(): string {
return `Subscriber (${this.memberDiscountRate * 100}% off)`;
}
}
class SeasonalPricing implements PricingStrategy {
constructor(
private seasonalMultiplier: number,
private seasonName: string
) {}
calculatePrice(basePrice: number, quantity: number): number {
return basePrice * quantity * this.seasonalMultiplier;
}
getName(): string {
return `Seasonal - ${this.seasonName}`;
}
}
// Contexto
class ShoppingCart {
private items: Array<{ name: string; price: number; quantity: number }> = [];
private pricingStrategy: PricingStrategy = new RegularPricing();
addItem(name: string, price: number, quantity: number): void {
this.items.push({ name, price, quantity });
}
setPricingStrategy(strategy: PricingStrategy): void {
this.pricingStrategy = strategy;
}
calculateTotal(): number {
return this.items.reduce((total, item) => {
return total + this.pricingStrategy.calculatePrice(item.price, item.quantity);
}, 0);
}
getReceipt(): string {
const lines = this.items.map(item => {
const subtotal = this.pricingStrategy.calculatePrice(item.price, item.quantity);
return `${item.name} x${item.quantity}: R$${subtotal}`;
});
return [
`Pricing: ${this.pricingStrategy.getName()}`,
'---',
...lines,
'---',
`Total: R$${this.calculateTotal()}`,
].join('\n');
}
}
// Exemplo de uso
const cart = new ShoppingCart();
cart.addItem('Widget', 1000, 5);
cart.addItem('Gadget', 2000, 3);
console.log(cart.getReceipt());
// Pricing: Regular
// Total: R$11000
cart.setPricingStrategy(new BulkPricing(3, 0.15));
console.log(cart.getReceipt());
// Pricing: Bulk (15% off for 3+)
// Total: R$9350
Padrao Observer
Define uma dependencia um-para-muitos entre objetos.
// Implementacao de Observer type-safe em TypeScript
type EventMap = {
userCreated: { id: string; email: string };
userUpdated: { id: string; changes: Partial<User> };
userDeleted: { id: string };
orderPlaced: { orderId: string; userId: string; total: number };
};
type EventKey = keyof EventMap;
type EventHandler<K extends EventKey> = (event: EventMap[K]) => void | Promise<void>;
class TypedEventEmitter {
private handlers = new Map<EventKey, Set<EventHandler<any>>>();
on<K extends EventKey>(event: K, handler: EventHandler<K>): () => void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
// Retorna funcao de cancelamento de inscricao
return () => this.off(event, handler);
}
off<K extends EventKey>(event: K, handler: EventHandler<K>): void {
this.handlers.get(event)?.delete(handler);
}
async emit<K extends EventKey>(event: K, data: EventMap[K]): Promise<void> {
const eventHandlers = this.handlers.get(event);
if (!eventHandlers) return;
const promises = Array.from(eventHandlers).map(handler =>
Promise.resolve(handler(data))
);
await Promise.all(promises);
}
once<K extends EventKey>(event: K, handler: EventHandler<K>): () => void {
const wrappedHandler: EventHandler<K> = async (data) => {
this.off(event, wrappedHandler);
await handler(data);
};
return this.on(event, wrappedHandler);
}
}
// Exemplo de uso
const eventBus = new TypedEventEmitter();
// Servico de envio de email
eventBus.on('userCreated', async ({ email }) => {
console.log(`Sending welcome email to ${email}`);
});
// Servico de analytics
eventBus.on('userCreated', ({ id }) => {
console.log(`Tracking user creation: ${id}`);
});
// Processamento de pedido
eventBus.on('orderPlaced', async ({ orderId, userId, total }) => {
console.log(`Processing order ${orderId} for user ${userId}: R$${total}`);
});
// Disparo de evento
await eventBus.emit('userCreated', {
id: 'user-123',
email: 'user@example.com',
});
Padrao Command
Encapsula operacoes como objetos.
// Implementacao de Command com funcionalidade Undo/Redo
interface Command {
execute(): Promise<void>;
undo(): Promise<void>;
getDescription(): string;
}
// Exemplo de comandos de editor de texto
class TextEditor {
private content: string = '';
getContent(): string {
return this.content;
}
setContent(content: string): void {
this.content = content;
}
insertAt(position: number, text: string): void {
this.content =
this.content.slice(0, position) + text + this.content.slice(position);
}
deleteRange(start: number, end: number): string {
const deleted = this.content.slice(start, end);
this.content = this.content.slice(0, start) + this.content.slice(end);
return deleted;
}
}
class InsertTextCommand implements Command {
constructor(
private editor: TextEditor,
private position: number,
private text: string
) {}
async execute(): Promise<void> {
this.editor.insertAt(this.position, this.text);
}
async undo(): Promise<void> {
this.editor.deleteRange(this.position, this.position + this.text.length);
}
getDescription(): string {
return `Insert "${this.text}" at position ${this.position}`;
}
}
class DeleteTextCommand implements Command {
private deletedText: string = '';
constructor(
private editor: TextEditor,
private start: number,
private end: number
) {}
async execute(): Promise<void> {
this.deletedText = this.editor.deleteRange(this.start, this.end);
}
async undo(): Promise<void> {
this.editor.insertAt(this.start, this.deletedText);
}
getDescription(): string {
return `Delete from ${this.start} to ${this.end}`;
}
}
// Command Manager (Invoker)
class CommandManager {
private history: Command[] = [];
private redoStack: Command[] = [];
async execute(command: Command): Promise<void> {
await command.execute();
this.history.push(command);
this.redoStack = []; // Limpa a pilha de redo ao executar novo comando
}
async undo(): Promise<boolean> {
const command = this.history.pop();
if (!command) return false;
await command.undo();
this.redoStack.push(command);
return true;
}
async redo(): Promise<boolean> {
const command = this.redoStack.pop();
if (!command) return false;
await command.execute();
this.history.push(command);
return true;
}
getHistory(): string[] {
return this.history.map(cmd => cmd.getDescription());
}
}
// Exemplo de uso
const editor = new TextEditor();
const manager = new CommandManager();
await manager.execute(new InsertTextCommand(editor, 0, 'Hello '));
await manager.execute(new InsertTextCommand(editor, 6, 'World!'));
console.log(editor.getContent()); // "Hello World!"
await manager.undo();
console.log(editor.getContent()); // "Hello "
await manager.redo();
console.log(editor.getContent()); // "Hello World!"
Padroes de Arquitetura Modernos
Padrao Repository
Abstrai a logica de acesso a dados.
// Padrao Repository com TypeScript
interface Entity {
id: string;
}
interface Repository<T extends Entity> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
findBy(criteria: Partial<T>): Promise<T[]>;
save(entity: T): Promise<T>;
delete(id: string): Promise<boolean>;
}
interface User extends Entity {
id: string;
email: string;
name: string;
createdAt: Date;
}
// Implementacao em memoria (para testes)
class InMemoryUserRepository implements Repository<User> {
private users: Map<string, User> = new Map();
async findById(id: string): Promise<User | null> {
return this.users.get(id) || null;
}
async findAll(): Promise<User[]> {
return Array.from(this.users.values());
}
async findBy(criteria: Partial<User>): Promise<User[]> {
return Array.from(this.users.values()).filter(user =>
Object.entries(criteria).every(
([key, value]) => user[key as keyof User] === value
)
);
}
async save(entity: User): Promise<User> {
this.users.set(entity.id, entity);
return entity;
}
async delete(id: string): Promise<boolean> {
return this.users.delete(id);
}
}
// Implementacao com Prisma (para producao)
class PrismaUserRepository implements Repository<User> {
constructor(private prisma: PrismaClient) {}
async findById(id: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { id } });
}
async findAll(): Promise<User[]> {
return this.prisma.user.findMany();
}
async findBy(criteria: Partial<User>): Promise<User[]> {
return this.prisma.user.findMany({ where: criteria });
}
async save(entity: User): Promise<User> {
return this.prisma.user.upsert({
where: { id: entity.id },
update: entity,
create: entity,
});
}
async delete(id: string): Promise<boolean> {
try {
await this.prisma.user.delete({ where: { id } });
return true;
} catch {
return false;
}
}
}
Padrao Unit of Work
Rastreia e gerencia alteracoes dentro de uma transacao.
// Implementacao de Unit of Work
interface UnitOfWork {
begin(): Promise<void>;
commit(): Promise<void>;
rollback(): Promise<void>;
userRepository: Repository<User>;
orderRepository: Repository<Order>;
}
class PrismaUnitOfWork implements UnitOfWork {
private transaction: Prisma.TransactionClient | null = null;
private _userRepository: Repository<User> | null = null;
private _orderRepository: Repository<Order> | null = null;
constructor(private prisma: PrismaClient) {}
async begin(): Promise<void> {
// Transacao interativa do Prisma
return new Promise((resolve) => {
this.prisma.$transaction(async (tx) => {
this.transaction = tx;
resolve();
// A transacao e mantida ate commit/rollback
});
});
}
get userRepository(): Repository<User> {
if (!this._userRepository) {
this._userRepository = new PrismaUserRepository(
this.transaction || this.prisma
);
}
return this._userRepository;
}
get orderRepository(): Repository<Order> {
if (!this._orderRepository) {
this._orderRepository = new PrismaOrderRepository(
this.transaction || this.prisma
);
}
return this._orderRepository;
}
async commit(): Promise<void> {
// Transacao Prisma faz auto-commit
this.transaction = null;
}
async rollback(): Promise<void> {
throw new Error('Rollback requested');
}
}
// Exemplo de uso
async function createOrderWithUser(uow: UnitOfWork) {
await uow.begin();
try {
const user = await uow.userRepository.save({
id: crypto.randomUUID(),
email: 'new@example.com',
name: 'New User',
createdAt: new Date(),
});
const order = await uow.orderRepository.save({
id: crypto.randomUUID(),
userId: user.id,
total: 5000,
status: 'pending',
});
await uow.commit();
return { user, order };
} catch (error) {
await uow.rollback();
throw error;
}
}
Relacao com Principios SOLID
flowchart LR
subgraph SOLID["Principios SOLID"]
S["S - Principio da Responsabilidade Unica (SRP)"]
O["O - Principio Aberto-Fechado (OCP)"]
L["L - Principio da Substituicao de Liskov (LSP)"]
I["I - Principio da Segregacao de Interface (ISP)"]
D["D - Principio da Inversao de Dependencia (DIP)"]
end
subgraph Patterns["Padroes Relacionados"]
P1["Factory, Strategy, Command"]
P2["Strategy, Decorator, Template Method"]
P3["Factory Method, Abstract Factory"]
P4["Adapter, Facade"]
P5["Repository, Dependency Injection"]
end
S --> P1
O --> P2
L --> P3
I --> P4
D --> P5
Resumo
Design Patterns sao uma linguagem comum para resolucao de problemas e tambem ajudam no alinhamento de equipes em desenvolvimento colaborativo.
Diretrizes de Selecao
| Objetivo | Padrao Recomendado |
|---|---|
| Flexibilidade na criacao de objetos | Factory, Builder |
| Extensao de codigo existente | Decorator, Adapter |
| Troca de algoritmos | Strategy |
| Orientacao a eventos | Observer |
| Desfazer operacoes | Command |
| Abstracao de acesso a dados | Repository |
Melhores Praticas em Desenvolvimento Moderno
- Evite aplicacao excessiva de padroes - Solucoes simples para problemas simples
- Aproveite recursos da linguagem - Sistema de tipos e decorators de TypeScript/Python
- Priorize testabilidade - Use DI para permitir injecao de dependencias
- Combine com abordagem funcional - Use funcoes para processamento sem estado
Design Patterns sao um meio, nao um fim. E importante entender o problema e selecionar o padrao apropriado.