Padrões de projeto OOP são soluções comprovadas para problemas de arquitetura comuns. Este guia prático explica padrões criacionais, estruturais e comportamentais com exemplos em TypeScript para você aplicar hoje e escrever código mais limpo, testável e escalável.
December 20, 2025 (4mo ago) — last updated April 25, 2026 (4d ago)
Guia Prático de Padrões de Projeto OOP
Aprenda padrões criacionais, estruturais e comportamentais com exemplos práticos em TypeScript para melhorar manutenção, extensibilidade e testabilidade do seu código.
← Back to blog
Guia Prático de Padrões de Projeto OOP
Domine padrões de projeto OOP com nosso guia. Aprenda padrões criacionais, estruturais e comportamentais com exemplos de código claros e práticos para elevar suas habilidades.
Introdução
Padrões de projeto em programação orientada a objetos são roteiros comprovados e reutilizáveis para resolver desafios de design recorrentes. Eles não são código para copiar e colar; são modelos adaptáveis que ajudam a estruturar classes e objetos para que seu código seja mais fácil de manter, estender e testar.
Compreender padrões criacionais, estruturais e comportamentais permite comunicar a intenção de design com colegas, refatorar código legado com segurança e escolher a solução certa para problemas arquiteturais comuns.
O que são padrões de projeto em programação orientada a objetos
Programar sem padrões de projeto pode levar a uma base de código emaranhada, difícil de escalar e manter. Padrões de projeto funcionam como receitas testadas que você adapta para produzir resultados consistentes e fáceis de manter.

A linguagem compartilhada dos desenvolvedores
Um dos benefícios mais valiosos dos padrões é um vocabulário compartilhado. Diga “Factory” ou “Singleton” e desenvolvedores experientes entendem imediatamente a intenção e a estrutura em alto nível, o que agiliza colaboração e decisões arquiteturais.
“Design Patterns: Elements of Reusable Object-Oriented Software,” publicado em 1994 por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, catalogou 23 padrões fundamentais que continuam leitura essencial para praticantes de OOP2. Trabalhos acadêmicos anteriores também demonstram benefícios mensuráveis de produtividade e reutilização em projetos grandes1.
Um padrão de projeto não é um design acabado que pode ser transformado diretamente em código. É uma descrição ou modelo de como resolver um problema que pode ser usado em muitas situações diferentes.
Ficar confortável com princípios centrais de OOP como polimorfismo e herança ajuda a aplicar padrões de forma eficaz. Para um resumo prático, veja nosso guia comparando polimorfismo vs herança.
As três categorias principais de padrões de projeto
O Gang of Four organizou os padrões em três categorias: criacionais, estruturais e comportamentais. Conhecer essas categorias ajuda a escolher rapidamente a abordagem certa para um problema.

Visão geral das categorias de padrões de projeto OOP
| Categoria de Padrão | Propósito Principal | Exemplos Comuns |
|---|---|---|
| Criacional | Gerenciar e abstrair a criação de objetos | Factory, Builder, Singleton, Prototype |
| Estrutural | Compor classes e objetos em estruturas maiores e flexíveis | Adapter, Decorator, Facade, Composite |
| Comportamental | Definir como objetos interagem e se comunicam | Observer, Strategy, Command, Iterator |
Padrões criacionais: os especialistas em construção
Padrões criacionais controlam como objetos são criados para que o código cliente não fique fortemente acoplado a classes concretas. Eles ocultam a lógica de criação, aumentam a flexibilidade e permitem gerenciar instanciação.
Padrões estruturais: a cola arquitetural
Padrões estruturais ajudam a montar objetos em sistemas maiores. Eles simplificam relações entre componentes para que você possa alterar partes sem quebrar todo o sistema. Padrões como Adapter permitem que interfaces incompatíveis cooperem, o que é inestimável ao integrar bibliotecas de terceiros.
Padrões estruturais simplificam o design do sistema ao identificar maneiras simples de realizar relações entre entidades.
Padrões comportamentais: os diretores da comunicação
Padrões comportamentais regem a interação e responsabilidade entre objetos. Eles criam canais de comunicação limpos—Observer para notificações dirigidas por eventos e Strategy para trocar algoritmos sem modificar clientes.
Ao gerenciar cuidadosamente os caminhos de comunicação, você reduz dependências e torna a base de código mais fácil de entender e manter.
Criando objetos com padrões criacionais
Padrões criacionais introduzem uma camada de abstração em torno da criação de objetos, para que você possa trocar implementações sem alterar o código cliente. Abaixo estão dois exemplos práticos em TypeScript: Singleton e Factory Method.

O padrão Singleton: garantindo uma instância verdadeira
Singleton garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global. É útil para recursos compartilhados como uma conexão de banco de dados ou logger, mas pode introduzir estado global que complica testes e gerenciamento de dependências3.
Exemplo: um Singleton em TypeScript para uma conexão de banco de dados.
class DatabaseConnection {
private static instance: DatabaseConnection;
private constructor() {
// A private constructor prevents external 'new' calls
console.log("Connecting to the database...");
}
public static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
public query(sql: string): void {
console.log(`Executing query: ${sql}`);
}
}
// Usage
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
db1.query("SELECT * FROM users");
console.log(db1 === db2); // true
A troca do Singleton: use Singletons para recursos verdadeiramente globais e prefira injeção de dependência para melhor testabilidade e modularidade3.
Factory Method: deixando subclasses decidirem o que criar
Factory Method define uma interface para criar um objeto, enquanto subclasses decidem qual produto concreto instanciar. Isso desacopla o código cliente de classes concretas, tornando o sistema mais fácil de estender.
Exemplo: renderizando botões específicos do SO em TypeScript.
interface Button {
render(): void;
onClick(f: () => void): void;
}
class WindowsButton implements Button {
render() { console.log("Rendering a button in Windows style."); }
onClick(f: () => void) { console.log("Windows button click event."); f(); }
}
class MacButton implements Button {
render() { console.log("Rendering a button in macOS style."); }
onClick(f: () => void) { console.log("Mac button click event."); f(); }
}
abstract class Dialog {
abstract createButton(): Button;
render() {
const okButton = this.createButton();
okButton.render();
}
}
class WindowsDialog extends Dialog {
createButton(): Button { return new WindowsButton(); }
}
class MacDialog extends Dialog {
createButton(): Button { return new MacButton(); }
}
// Client
const os: string = "windows";
let dialog: Dialog;
if (os === "windows") dialog = new WindowsDialog(); else dialog = new MacDialog();
dialog.render();
Factory Method mantém o criador alheio aos produtos concretos, tornando adições como um LinuxDialog diretas.
Construindo sistemas flexíveis com padrões estruturais
Padrões estruturais ajudam a compor objetos em sistemas robustos e adaptáveis. Duas escolhas práticas são Adapter e Decorator.

O padrão Adapter: ligando interfaces incompatíveis
Adapter adapta uma interface incompatível para que ela se conforme ao que seu sistema espera. Isso evita mudanças invasivas em código legado.
Exemplo: adaptando um ModernLogger para uma interface ILogger existente.
class ModernLogger {
public logInfo(message: string): void {
console.log(`[INFO]: ${message}`);
}
}
interface ILogger { log(message: string): void; }
class LoggerAdapter implements ILogger {
private modernLogger: ModernLogger;
constructor() { this.modernLogger = new ModernLogger(); }
public log(message: string): void { this.modernLogger.logInfo(message); }
}
const logger: ILogger = new LoggerAdapter();
logger.log("User logged in successfully.");
O padrão Decorator: adicionando funcionalidade dinamicamente
Decorator adiciona responsabilidades a objetos em tempo de execução envolvendo-os. É mais flexível do que herança e segue o Princípio da Responsabilidade Única.
Exemplo: compondo uma assinatura com complementos opcionais.
interface Subscription { getDescription(): string; getCost(): number; }
class BasicSubscription implements Subscription {
getDescription(): string { return "Basic Plan"; }
getCost(): number { return 10; }
}
abstract class SubscriptionDecorator implements Subscription {
protected subscription: Subscription;
constructor(subscription: Subscription) { this.subscription = subscription; }
abstract getDescription(): string;
abstract getCost(): number;
}
class PremiumSupportDecorator extends SubscriptionDecorator {
getDescription(): string { return `${this.subscription.getDescription()}, Premium Support`; }
getCost(): number { return this.subscription.getCost() + 5; }
}
class CloudStorageDecorator extends SubscriptionDecorator {
getDescription(): string { return `${this.subscription.getDescription()}, 1TB Cloud Storage`; }
getCost(): number { return this.subscription.getCost() + 7; }
}
let mySubscription: Subscription = new BasicSubscription();
mySubscription = new PremiumSupportDecorator(mySubscription);
mySubscription = new CloudStorageDecorator(mySubscription);
console.log(mySubscription.getDescription());
console.log(mySubscription.getCost());
Essa abordagem composicional mantém os recursos modulares e fáceis de testar.
Gerenciando interações com padrões comportamentais
Padrões comportamentais organizam a comunicação entre objetos para que os sistemas permaneçam flexíveis e fáceis de manter. Dois exemplos centrais são Observer e Strategy.
O padrão Observer: notificando partes interessadas
Observer estabelece uma relação um-para-muitos para que, quando um Subject muda de estado, os Observers sejam notificados automaticamente. É ideal para sistemas dirigidos por eventos.
Exemplo: um serviço simples de notificações.
interface Subject { attach(observer: Observer): void; detach(observer: Observer): void; notify(): void; }
interface Observer { update(subject: Subject): void; }
class NotificationService implements Subject {
public state: string = '';
private observers: Observer[] = [];
attach(observer: Observer): void { this.observers.push(observer); }
detach(observer: Observer): void { const i = this.observers.indexOf(observer); if (i !== -1) this.observers.splice(i, 1); }
notify(): void { for (const o of this.observers) o.update(this); }
public createNewPost(title: string): void { this.state = `New Post: ${title}`; console.log(`\nNotificationService: A new post was created.`); this.notify(); }
}
class EmailNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`EmailNotifier: Sending email about "${subject.state}"`); } }
class PushNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`PushNotifier: Sending push notification for "${subject.state}"`); } }
const notificationService = new NotificationService();
const emailer = new EmailNotifier();
const pusher = new PushNotifier();
notificationService.attach(emailer);
notificationService.attach(pusher);
notificationService.createNewPost("Understanding Observer Pattern");
notificationService.detach(pusher);
notificationService.createNewPost("Why Strategy is Awesome");
Observer produz um design fracamente acoplado: Subjects não precisam conhecer os Observers concretos que notificam.
O padrão Strategy: encapsulando algoritmos
Strategy permite trocar algoritmos em tempo de execução, evitando grandes blocos condicionais. Ele alinha-se com o Princípio Aberto/Fechado ao permitir novas estratégias sem modificar o código existente4.
Exemplo: estratégias de pagamento para um carrinho de compras.
interface PaymentStrategy { pay(amount: number): void; }
class CreditCardStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} with Credit Card.`); } }
class PayPalStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} via PayPal.`); } }
class ShoppingCart {
private paymentStrategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
public setPaymentStrategy(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
public checkout(amount: number): void { this.paymentStrategy.pay(amount); }
}
const cart = new ShoppingCart(new CreditCardStrategy());
cart.checkout(150);
cart.setPaymentStrategy(new PayPalStrategy());
cart.checkout(150);
Strategy remove a complexidade condicional e facilita a extensão dos sistemas.
Armadilhas comuns de design e estratégias de refatoração
Escolher um padrão é apenas metade da batalha. Evite anti-padrões como o God Object, que acumula responsabilidades e viola o Princípio da Responsabilidade Única. Procure por cheiros de código—métodos longos, dependências excessivas ou classes monstruosas—e trate-os com refatoração disciplinada.
Refatorar é melhorar a estrutura interna sem alterar o comportamento externo. É assim que você doma sistemas legados com segurança.
Refatorando para o padrão Strategy
Um grande switch ou uma longa cadeia if/else é um cheiro clássico. Refatore extraindo o comportamento variável para uma interface Strategy com classes de estratégia concretas. Substitua o condicional por uma única chamada ao método da estratégia selecionada. Essa mudança melhora extensibilidade, testabilidade e alinhamento com os princípios SOLID4.
Perguntas rápidas (Q&A)
O que devo aprender primeiro?
Concentre-se em 5–7 padrões essenciais: Singleton, Factory, Adapter, Decorator, Observer e Strategy. Entenda o problema que cada padrão resolve e pratique com exemplos.
Quando não devo usar um padrão?
Evite padrões que adicionam complexidade sem resolver um problema real. Não introduza um padrão por precaução; aplique o YAGNI e escolha padrões quando uma necessidade concreta aparecer.
Padrões funcionam com programação funcional?
Sim. Muitos padrões mapeiam bem para técnicas funcionais. Strategy pode ser uma função passada como parâmetro, e Decorator pode ser uma função de ordem superior. Foque no princípio, não na forma.
Na Clean Code Guy, ajudamos equipes a construir software que dura incorporando princípios fundamentais em seu fluxo de trabalho. Descubra como nossas auditorias de código e refatoração preparada para IA podem fazer sua equipe entregar com confiança em https://cleancodeguy.com.
IA escreve código.Você faz durar.
Na era da aceleração da IA, código limpo não é apenas uma boa prática — é a diferença entre sistemas que escalam e bases de código que entram em colapso sob seu próprio peso.