Pular para o conteúdo

Padrões Criacionais

Quando o código faz new ClasseConcreta() diretamente, ele fica amarrado a essa classe. Os padrões criacionais movem essa decisão para trás de uma abstração, permitindo variar o tipo e o processo de criação sem mexer no cliente. Isso realiza OCP (novas variações entram como novas classes) e DIP (o cliente depende de abstrações).

Intenção: define uma interface para criar um objeto, mas deixa subclasses decidirem qual classe concreta instanciar.

Quando usar: quando uma classe não sabe de antemão a classe concreta que deve criar, ou quer delegar essa escolha a subclasses. Comum em frameworks que definem o esqueleto e deixam o cliente preencher o “que” é criado.

Intenção: fornece uma interface para criar famílias de objetos relacionados sem especificar suas classes concretas.

Quando usar: quando o sistema deve ser independente de como seus produtos são criados e precisa garantir que produtos de uma mesma família sejam usados juntos (ex: um kit de widgets por tema de UI). Contraste detalhado com Factory Method na nota Abstract Factory.

Intenção: separa a construção de um objeto complexo de sua representação, permitindo montá-lo passo a passo.

Quando usar: quando a criação envolve muitos parâmetros (especialmente opcionais), montagem em várias etapas ou variações de representação. Evita o telescoping constructor. Detalhes e alternativa a record na nota Builder.

Intenção: cria novos objetos clonando uma instância protótipo, em vez de instanciar a classe do zero.

Quando usar: quando o custo de criar do zero é alto, quando as classes a instanciar são definidas em tempo de execução, ou quando se quer evitar uma hierarquia de fábricas paralela à de produtos. Em Java, envolve clone() (raso vs. profundo) ou construtores de cópia.

Intenção: garante que uma classe tenha uma única instância e fornece um ponto global de acesso a ela.

Quando usar: quando é logicamente necessário exatamente um objeto (um registro de configuração imutável, um pool). Fora esses casos, geralmente há alternativa melhor.

O Singleton é o padrão criacional mais abusado. Seus custos costumam superar os benefícios:

  • Acoplamento global disfarçado: Config.getInstance() espalhado pelo código cria dependências ocultas — nenhuma assinatura de método revela que a classe depende do singleton. Isso viola o espírito do DIP: em vez de receber a dependência, o objeto vai buscá-la globalmente.
  • Atrapalha testes: estado global compartilhado entre testes causa acoplamento temporal e ordem-dependência; substituir por um dublê exige truques (reflection, subclasses). Ver Testabilidade e Arquitetura.
  • Esconde a verdadeira cardinalidade: quase sempre o que se quer é “uma instância gerenciada pelo container”, não “uma instância imposta pela própria classe”.

Alternativa recomendada: injetar a dependência (um único bean com escopo singleton no container de DI). O ciclo de vida vira responsabilidade da composição da aplicação, não da classe, preservando testabilidade e explicitando a dependência.

public enum ConfiguracaoApp {
INSTANCIA;
private final String url = "jdbc:postgresql://localhost/app";
public String url() {
return url;
}
}

O enum acima é a forma segura de Singleton em Java (serialização e concorrência resolvidas pela JVM) — mas prefira DI mesmo assim, exceto para constantes verdadeiramente imutáveis.

classDiagram
    class Cliente
    class Criador {
        <<abstract>>
        +criar() Produto
    }
    class Produto {
        <<interface>>
    }
    class ProdutoConcretoA
    class ProdutoConcretoB
    Cliente --> Criador : usa
    Cliente --> Produto : consome abstração
    Criador ..> Produto : cria
    Produto <|.. ProdutoConcretoA
    Produto <|.. ProdutoConcretoB
  • Singleton como variável global: usar Singleton para carregar estado mutável acessível de qualquer lugar. É acoplamento global com outro nome.
  • Fábrica que não abstrai nada: uma “factory” que só encapsula um único new sem ponto de variação é indireção inútil.
  • Builder para objetos triviais: um Builder para uma classe de dois campos obrigatórios é over-engineering — um construtor ou record basta.
  • Prototype com clone() raso onde precisava ser profundo: compartilhar referências mutáveis entre clones gera bugs sutis.

1. Qual problema comum todos os padrões criacionais atacam?

Resposta

A dependência direta do cliente em classes concretas via new. Eles encapsulam qual classe instanciar e como montá-la atrás de uma abstração, favorecendo OCP e DIP.

2. Por que o Singleton é problemático para testes?

Resposta

Ele impõe estado global compartilhado e acesso via método estático, criando dependências ocultas e acoplamento entre testes (ordem-dependência). Substituí-lo por um dublê é difícil. A alternativa é injeção de dependência com escopo singleton gerenciado pelo container.

3. Quando escolher Builder em vez de um construtor simples?

Resposta

Quando há muitos parâmetros (especialmente opcionais), montagem em etapas ou diferentes representações do mesmo processo de construção. Para objetos simples, prefira construtor ou record — Builder aí seria over-engineering.

4. Qual a diferença essencial entre Factory Method e Abstract Factory?

Resposta

Factory Method cria um produto e delega a escolha da classe concreta a subclasses (via herança/sobrescrita de um método). Abstract Factory cria famílias de produtos relacionados através de composição de um objeto-fábrica. Ver Abstract Factory.