Padrões criacionais: Singleton, Factory Method, Abstract Factory
1. Introdução aos Padrões Criacionais na Arquitetura de Software
Os padrões criacionais são um grupo de soluções arquiteturais que tratam dos mecanismos de criação de objetos, buscando aumentar a flexibilidade e a reutilização do código. Em vez de instanciar classes diretamente com o operador new, esses padrões delegam a responsabilidade de criação para métodos ou objetos especializados, promovendo um baixo acoplamento entre o código cliente e as classes concretas.
No contexto da Arquitetura de Software, esses padrões se relacionam diretamente com os princípios SOLID, especialmente o Princípio da Responsabilidade Única (SRP) e o Princípio da Inversão de Dependência (DIP). Ao separar a lógica de criação da lógica de negócio, o sistema torna-se mais modular e adaptável a mudanças. A Lei de Demeter também é beneficiada, pois os objetos passam a conhecer menos detalhes sobre a estrutura interna de outros objetos.
Os três padrões abordados neste artigo — Singleton, Factory Method e Abstract Factory — resolvem problemas distintos de criação. O Singleton garante uma única instância de uma classe; o Factory Method delega a criação para subclasses; e o Abstract Factory coordena a criação de famílias de objetos relacionados. Cada um atende a diferentes necessidades arquiteturais.
2. Singleton: Garantindo Instância Única
O padrão Singleton tem como objetivo assegurar que uma classe possua apenas uma instância em todo o sistema, fornecendo um ponto de acesso global a ela. É comumente utilizado para gerenciar recursos compartilhados, como conexões de banco de dados, sistemas de logging ou configurações globais.
A implementação clássica envolve um construtor privado, um atributo estático privado que armazena a única instância e um método estático público que retorna essa instância. Quando o método é chamado pela primeira vez, a instância é criada; nas chamadas subsequentes, a mesma instância é retornada.
class Configuracao {
private static instancia: Configuracao
private dados: Map<String, String>
private constructor() {
this.dados = new Map()
}
public static getInstancia(): Configuracao {
if (Configuracao.instancia == null) {
Configuracao.instancia = new Configuracao()
}
return Configuracao.instancia
}
public getValor(chave: String): String {
return this.dados.get(chave)
}
public setValor(chave: String, valor: String): void {
this.dados.put(chave, valor)
}
}
Apesar de sua simplicidade, o Singleton apresenta problemas arquiteturais significativos. Ele introduz acoplamento global, pois qualquer parte do sistema pode acessar a instância diretamente, dificultando a substituição em testes unitários. A concorrência também é uma preocupação: em ambientes multithread, é necessário sincronizar o acesso ao método de criação para evitar múltiplas instâncias.
3. Factory Method: Delegando a Criação para Subclasses
O Factory Method define uma interface para criar um objeto, mas permite que as subclasses decidam qual classe concreta instanciar. Esse padrão é útil quando uma classe não pode antecipar a classe de objetos que deve criar, ou quando deseja-se que subclasses especifiquem os objetos criados.
Em termos arquiteturais, o Factory Method promove a inversão de dependências: a classe cliente depende de uma abstração (a interface do produto), enquanto a criação concreta é delegada a subclasses especializadas. Um exemplo típico é a criação de conexões de banco de dados, onde diferentes bancos (MySQL, PostgreSQL, Oracle) exigem implementações distintas.
abstract class CriadorDocumento {
public abstract criarDocumento(): Documento
public gerarRelatorio(): void {
Documento doc = this.criarDocumento()
doc.abrir()
doc.escrever("Conteúdo do relatório")
doc.fechar()
}
}
class CriadorPDF extends CriadorDocumento {
public criarDocumento(): Documento {
return new DocumentoPDF()
}
}
class CriadorHTML extends CriadorDocumento {
public criarDocumento(): Documento {
return new DocumentoHTML()
}
}
A principal vantagem é a flexibilidade para extensão: novos tipos de documentos podem ser adicionados sem modificar o código existente, respeitando o Princípio do Aberto/Fechado (OCP). O Factory Method também facilita a testabilidade, pois permite injetar objetos mock durante os testes.
4. Abstract Factory: Famílias de Objetos Relacionados
O Abstract Factory fornece uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas. É especialmente útil em cenários onde o sistema precisa suportar múltiplos ambientes, como diferentes sistemas operacionais ou temas de interface.
Diferentemente do Factory Method, que lida com a criação de um único objeto, o Abstract Factory opera em nível de grupo, coordenando a criação de vários objetos que devem ser compatíveis entre si. Por exemplo, ao criar uma interface gráfica para Windows e Linux, é necessário garantir que botões, janelas e menus sejam consistentes dentro de cada plataforma.
interface FabricaUI {
criarBotao(): Botao
criarJanela(): Janela
criarMenu(): Menu
}
class FabricaWindows implements FabricaUI {
public criarBotao(): Botao {
return new BotaoWindows()
}
public criarJanela(): Janela {
return new JanelaWindows()
}
public criarMenu(): Menu {
return new MenuWindows()
}
}
class FabricaLinux implements FabricaUI {
public criarBotao(): Botao {
return new BotaoLinux()
}
public criarJanela(): Janela {
return new JanelaLinux()
}
public criarMenu(): Menu {
return new MenuLinux()
}
}
A comparação com o Factory Method é direta: enquanto o Factory Method é um método individual para criar um produto, o Abstract Factory é uma interface completa para criar uma família de produtos. Ambos podem ser combinados, com o Abstract Factory utilizando Factory Methods internos para cada produto.
5. Singleton vs. Factory Method vs. Abstract Factory: Quando Usar Cada Um
A escolha entre esses padrões depende do escopo, complexidade e necessidades de variação do sistema. O Singleton é indicado quando há a necessidade rigorosa de uma única instância, como em gerenciadores de configuração ou pools de conexão. No entanto, seu uso excessivo pode levar a um acoplamento forte e dificuldades de teste. Uma alternativa moderna é a injeção de dependência, que permite controlar o escopo das instâncias sem criar dependências globais.
O Factory Method é apropriado quando uma classe não pode prever o tipo de objeto que precisa criar, mas deseja delegar essa decisão para subclasses. É ideal para frameworks que precisam ser estendidos por desenvolvedores terceiros.
O Abstract Factory é a melhor opção quando o sistema precisa trabalhar com múltiplas famílias de produtos que devem ser usadas de forma intercambiável, como em aplicações multiplataforma ou com suporte a temas.
Esses padrões podem ser combinados: um Abstract Factory pode conter Factory Methods para cada produto, e um Singleton pode gerenciar a fábrica concreta selecionada em tempo de execução.
6. Exemplos de Código em Pseudocódigo
Singleton — Classe Configuracao
class Configuracao {
private static instancia: Configuracao
private mapa: Map<String, String>
private constructor() {
this.mapa = new Map()
}
public static getInstancia(): Configuracao {
if (this.instancia == null) {
this.instancia = new Configuracao()
}
return this.instancia
}
public get(chave: String): String {
return this.mapa.get(chave)
}
public set(chave: String, valor: String): void {
this.mapa.put(chave, valor)
}
}
Factory Method — CriadorDocumento
abstract class CriadorDocumento {
public abstract criarDocumento(): Documento
public processar(): void {
Documento doc = this.criarDocumento()
doc.abrir()
doc.salvar()
doc.fechar()
}
}
class CriadorPDF extends CriadorDocumento {
public criarDocumento(): Documento {
return new DocumentoPDF()
}
}
class CriadorWord extends CriadorDocumento {
public criarDocumento(): Documento {
return new DocumentoWord()
}
}
Abstract Factory — FabricaUI
interface FabricaUI {
criarBotao(): Botao
criarCaixaTexto(): CaixaTexto
}
class FabricaWindows implements FabricaUI {
public criarBotao(): Botao {
return new BotaoWindows()
}
public criarCaixaTexto(): CaixaTexto {
return new CaixaTextoWindows()
}
}
class FabricaLinux implements FabricaUI {
public criarBotao(): Botao {
return new BotaoLinux()
}
public criarCaixaTexto(): CaixaTexto {
return new CaixaTextoLinux()
}
}
7. Impacto Arquitetural e Boas Práticas
A adoção desses padrões criacionais impacta diretamente a modularidade e a testabilidade do sistema. O Singleton, quando mal utilizado, pode criar dependências ocultas e dificultar a substituição de implementações em testes. Por outro lado, o Factory Method e o Abstract Factory promovem um design mais desacoplado, facilitando a injeção de dependências e a criação de mocks.
Esses padrões também se relacionam com padrões estruturais como Adapter e Facade. Um Abstract Factory pode ser combinado com um Adapter para adaptar famílias de objetos a interfaces esperadas pelo sistema. Já o Facade pode simplificar a interface de criação oferecida pelo Abstract Factory.
Para boas práticas, recomenda-se documentar claramente a intenção de cada padrão utilizado, especialmente o Singleton, que pode ser confundido com uma variável global. A evolução do código deve considerar a possibilidade de substituir um Singleton por injeção de dependência quando o sistema crescer. Além disso, é importante evitar a criação de fábricas genéricas demais, que podem introduzir complexidade desnecessária.
Referências
- Singleton Pattern - Refactoring.Guru — Explicação detalhada do padrão Singleton com exemplos em várias linguagens e discussão sobre prós e contras.
- Factory Method Pattern - Refactoring.Guru — Guia completo do Factory Method, incluindo estrutura, implementação e relação com outros padrões.
- Abstract Factory Pattern - Refactoring.Guru — Tutorial sobre o Abstract Factory com exemplos práticos e comparação com o Factory Method.
- Design Patterns: Elements of Reusable Object-Oriented Software - GoF Book — O livro clássico que introduziu os padrões de design, incluindo Singleton, Factory Method e Abstract Factory.
- Singleton vs Dependency Injection - Martin Fowler — Artigo técnico de Martin Fowler discutindo alternativas ao Singleton e os benefícios da injeção de dependência.
- Abstract Factory Pattern in Java - Baeldung — Tutorial prático de implementação do Abstract Factory em Java, com exemplos de código e cenários de uso.