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
Cover Image for Guia Prático de Padrões de Projeto OOP

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.

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.

Um esboço de um chapéu de chef deixando cair formas de letras douradas em um livro de receitas aberto.

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.

Diagrama ilustrando padrões de projeto criacionais, estruturais e comportamentais com ilustrações simples de caixas.

Visão geral das categorias de padrões de projeto OOP

Categoria de PadrãoPropósito PrincipalExemplos Comuns
CriacionalGerenciar e abstrair a criação de objetosFactory, Builder, Singleton, Prototype
EstruturalCompor classes e objetos em estruturas maiores e flexíveisAdapter, Decorator, Facade, Composite
ComportamentalDefinir como objetos interagem e se comunicamObserver, 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.

Uma ilustração contrastando objetos individuais semelhantes a ovos com uma linha de montagem de fábrica produzindo itens coloridos.

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.

Ilustrações desenhadas à mão de um adaptador elétrico e uma pilha de anéis representando padrões de projeto OOP.

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.

1.
W. G. Griswold et al., “A Formal Foundation for Design Pattern Abstraction and Reuse,” Proceedings of ECOOP ’93, University of California, San Diego. https://cseweb.ucsd.edu/~wgg/CSE210/ecoop93-patterns.pdf
2.
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). https://en.wikipedia.org/wiki/Design_Patterns
3.
Martin Fowler, “Singleton,” Bliki, discusses Singleton trade-offs and testing implications. https://martinfowler.com/bliki/Singleton.html
4.
Robert C. Martin (Uncle Bob), “The SOLID Principles,” which explain Open/Closed and related design goals. https://8thlight.com/blog/uncle-bob/2012/08/13/the-s-o-l-i-d-principles.html
← Back to blog
🙋🏻‍♂️

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.