W3docs

Introdução aos Padrões de Projeto em Java

Introdução aos padrões de projeto em Java — o que são e como usar os mais comuns, como Strategy, Factory e Singleton.

Um padrão de projeto é uma solução nomeada e reutilizável para um problema que continua surgindo quando você desenvolve software. Padrões não são bibliotecas que você importa nem código que você copia — são formas de organizar classes e objetos que desenvolvedores experientes consolidaram ao longo do tempo. Aprendê-los lhe dá um vocabulário compartilhado: diga "vamos usar um Factory aqui" e outro desenvolvedor Java saberá imediatamente o que isso significa.

Esta página apresenta o que são padrões de projeto, as três famílias em que se dividem e três dos mais comuns — Strategy, Factory e Singleton — com um exemplo executável que os combina. Presume-se que você esteja familiarizado com interfaces e polimorfismo, pois praticamente todo padrão se baseia neles.

Os padrões foram popularizados pelo livro de 1994 Design Patterns, dos "Gang of Four", que catalogou 23 deles. Você não precisa de todos os 23 para começar. Um punhado, aplicado no momento certo, torna o código mais fácil de modificar sem quebrá-lo.

As três famílias

O catálogo clássico divide os padrões em três grupos de acordo com o que ajudam a resolver:

FamíliaPreocupaçãoExemplos
CriacionalComo os objetos são criadosSingleton, Factory, Builder
EstruturalComo os objetos são compostosAdapter, Decorator, Facade
ComportamentalComo os objetos interagemStrategy, Observer, Iterator

O próprio Java está repleto desses padrões. StringBuilder é um Builder, Iterator é o padrão Iterator, e java.util.logging usa Singletons. Você já vinha usando padrões sem nomeá-los.

Strategy: trocar o algoritmo

O padrão Strategy encapsula uma família de algoritmos intercambiáveis por trás de uma interface comum, para que o código chamador possa alternar entre eles sem precisar mudar. Defina a interface, escreva cada variante como sua própria classe e deixe um contexto manter aquela de que precisa.

interface DiscountStrategy {
    double apply(double price);
}

class PercentOff implements DiscountStrategy {
    private final double percent;
    PercentOff(double percent) { this.percent = percent; }
    public double apply(double price) { return price * (1 - percent / 100); }
}

Uma classe contexto mantém uma DiscountStrategy e delega a ela, em vez de ramificar com uma cadeia de if/switch. Adicionar um novo desconto significa adicionar uma classe — não editar o código existente. (O contexto Checkout completo, além das variantes NoDiscount e FlatOff, aparecem no demo executável abaixo.)

Factory: centralizar a criação

Um Factory é um único método (ou classe) responsável por decidir qual tipo concreto instanciar. Os chamadores pedem algo por descrição e recebem de volta um objeto que cumpre a interface, sem precisar conhecer a classe exata.

static DiscountStrategy forCustomer(String tier) {
    return switch (tier) {
        case "gold"   -> new PercentOff(20);
        case "silver" -> new PercentOff(10);
        default       -> new NoDiscount();
    };
}

A lógica de criação fica em um único lugar. Se as regras mudarem, você edita o factory — todos os chamadores continuam funcionando sem alterações.

Singleton: exatamente uma instância

Um Singleton garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. No Java moderno, um enum é a forma mais simples e thread-safe de escrever um — a JVM garante que suas constantes sejam criadas exatamente uma vez. Consulte The Singleton Pattern para as variantes de inicialização lazy e double-checked locking.

enum Config {
    INSTANCE;
    private final String env = "production";
    public String env() { return env; }
}

// usage
String e = Config.INSTANCE.env();

Use Singletons com parcimônia — eles introduzem estado global, o que dificulta os testes. Muitas vezes, um único objeto passado por meio de um construtor (injeção de dependência) é a escolha melhor.

Quando não recorrer a um padrão

Os padrões adicionam estrutura, e estrutura tem um custo. Um switch com dois casos não precisa do padrão Strategy; uma classe que você instancia uma única vez não precisa de um Factory. Recorra a um padrão quando sentir a dor que ele resolve — ramificações duplicadas, chamadas new espalhadas, conexões entre objetos emaranhadas — não antes. Aplicar padrões em excesso produz código mais difícil de ler do que o problema que pretendia simplificar.

Strategy e Factory juntos

O exemplo executável abaixo combina dois padrões. DiscountStrategy é a interface Strategy com três implementações; forCustomer é um Factory que escolhe uma delas. O contexto Checkout delega para qualquer strategy que mantiver, de modo que seu código nunca muda conforme o comportamento de precificação varia.

java— editable, runs on the server

O que observar na execução:

  • Cada nível imprime um total diferente — regular permanece em $100,00, silver cai para $90,00, gold para $80,00, coupon para $95,00 — mesmo que Checkout.total chame o mesmo método único.
  • O contexto Checkout nunca ramifica sobre o nível em si; ele simplesmente delega para qualquer DiscountStrategy que estiver mantendo no momento.
  • O Factory forCustomer é o único lugar que sabe qual classe concreta corresponde a qual nível, então a lógica de seleção fica em um único ponto.
  • Trocar o comportamento é apenas setStrategy(...) em tempo de execução — adicionar um quarto desconto significaria uma nova classe mais um caso no factory, sem nenhuma mudança em Checkout.
  • As linhas finais confirmam a strategy ativa: após re-selecionar gold, a política exibe 20.0% off e o total é $80,00, provando que o contexto reflete qualquer strategy definida por último.

Prática

Prática
Qual problema o padrão Strategy resolve?
Qual problema o padrão Strategy resolve?
Was this page helpful?