O Padrão Factory é um dos padrões de design mais empregados no desenvolvimento Java, concebido para desacoplar o processo de criação de objetos da sua utilização. Ele oferece uma abordagem flexível para instanciar classes, permitindo que a lógica de criação seja centralizada em uma entidade específica, conhecida como fábrica, em vez de espalhada por todo o código cliente.
Ao externalizar a responsabilidade de criar objetos para uma classe fábrica, o sistema ganha em manutenibilidade e extensibilidade. O código cliente interage apenas com a fábrica, solicitando um objeto por meio de um tipo genérico ou interface, sem precisar conhecer os detalhes específicos da sua implementação ou o processo exato de instanciamento.
Variantes do Padrão Factory
Existem três formas principais de implementar o Padrão Factory, cada uma com suas particularidades e casos de uso:
- Simple Factory (Fábrica Simples): Embora não seja um padrão de design formalmente reconhecido pelo GoF, a Fábrica Simples é um ponto de partida fundamental. Ela utiliza uma única classe para gerar diferentes tipos de objetos com base em um parâmetro fornecido, simplificando a decisão de qual objeto instanciar.
- Factory Method (Método Fábrica): Este padrão define uma interface para a criação de objetos, mas delega a decisão sobre qual classe concreta instanciar para as subclasses. O processo de criação é, assim, postergado para as implementações específicas da fábrica.
- Abstract Factory (Fábrica Abstrata): A Fábrica Abstrata fornece uma interface para a criação de famílias de objetos relacionados ou interdependentes, sem especificar suas classes concretas. É útil quando o sistema precisa ser independente de como seus produtos são criados, compostos e representados.
Propósito e Aplicação
O objetivo primordial do Padrão Factory é fornecer uma interface para a criação de objetos, permitindo que as subclasses determinem qual classe concrtea instanciar. Isso posterga a decisão de criação de objetos para as subclasses, resolvendo o problema de qual implementação de interface escolher dinamicamente.
Quando Utilizar
Este padrão é particularmente útil em cenários onde:
- É necessário criar diferentes instâncias de objetos sob diversas condições.
- Uma classe não pode antecipar o tipo de objetos que precisa criar.
- Uma classe quer que suas subclasses especifiquem os objetos que ela cria.
Benefícios
- O cliente da fábrica precisa apenas conhecer o nome ou tipo do objeto desejado, sem se preocupar com a sua classe concreta.
- Alta extensibilidade: Adicionar um novo tipo de produto geralmente requer apenas a criação de uma nova classe de produto e, dependendo da variante do padrão, uma pequena alteração na fábrica ou a adição de uma nova fábrica.
- A implementação específica dos produtos é encapsulada, expondo apenas uma interface comum aos clientes.
Considerações
Apesar de suas vantagens, o Padrão Factory pode introduzir complexidade adicional, especialmente o Método Fábrica, onde a adição de um novo produto pode implicar a criação de uma nova classe concreta de produto e, correspondentemente, uma nova classe concreta de fábrica. Isso pode levar a um aumento no número de classes no sistema.
É importante avaliar se a complexidade do padrão é justificada. Para objetos simples, cuja criação é direta (via operador new), o uso de uma fábrica pode ser um excesso.
Estrutura Componentes
O Padrão Factory (em suas formas mais elaboradas como Factory Method e Abstract Factory) é geralmente composto pelos seguintes elementos:
- Produto Abstrato (Abstract Product): Uma interface ou classe abstrata que declara a interface comum para todos os objetos que a fábrica pode criar.
- Produto Concreto (Concrete Product): Implementações específicas do Produto Abstrato, representando os objetos reais a serem criados.
- Fábrica Abstrata (Abstract Factory): Uma interface ou classe abstrata que declara o método(s) para criar os objetos do Produto Abstrato.
- Fábrica Concreta (Concrete Factory): Uma implementação da Fábrica Abstrata, responsável por instanciar os Produtos Concretos específicos.
Exemplo de Implementação em Java
Para ilustrar, criaremos um sistema para gerenciar diferentes formas geométricas. A ideia é que o cliente solicite uma forma por seu tipo e receba uma instância sem se preocupar com a lógica de criação.
1. Definindo a Interface do Produto Abstrato
Primeiro, definimos uma interface comum para todas as formas geométricas, que especifica a operação principal que elas devem realizar.
// FormaGeometrica.java
public interface FormaGeometrica {
void desenhar();
}
2. Implementando os Produtos Concretos
Em seguida, criamos as classes concretas que implementam a interface FormaGeometrica, cada uma com sua própria representação do método desenhar().
// Retangulo.java
public class Retangulo implements FormaGeometrica {
@Override
public void desenhar() {
System.out.println("Desenhando um Retângulo.");
}
}
// Quadrado.java
public class Quadrado implements FormaGeometrica {
@Override
public void desenhar() {
System.out.println("Desenhando um Quadrado.");
}
}
// Circulo.java
public class Circulo implements FormaGeometrica {
@Override
public void desenhar() {
System.out.println("Desenhando um Círculo.");
}
}
3. Criando um Enum para os Tipos de Forma
Para tornar a fábrica mais robusta e evitar erros de digitação de strings, podemos usar um enum para representar os tipos de formas.
// TipoForma.java
public enum TipoForma {
CIRCULO,
RETANGULO,
QUADRADO;
}
4. Desenvolvendo a Fábrica de Formas
Agora, criamos a classe fábrica que será responsável por instanciar a FormaGeometrica apropriada com base no TipoForma fornecido.
// FabricaDeFormas.java
public class FabricaDeFormas {
public FormaGeometrica criarForma(TipoForma tipo) {
if (tipo == null) {
return null;
}
switch (tipo) {
case CIRCULO:
return new Circulo();
case RETANGULO:
return new Retangulo();
case QUADRADO:
return new Quadrado();
default:
// Em um cenário real, poderia lançar uma exceção ou registrar um erro
return null;
}
}
}
5. Utilizando a Fábrica
Por fim, demonstramos como o código cliente interage com a fábrica para obter as instâncias das formas, sem precisar conhecer os construtores das classes concretas.
// TesteFabrica.java
public class TesteFabrica {
public static void main(String[] args) {
FabricaDeFormas fabrica = new FabricaDeFormas();
// Obtém um objeto Círculo e invoca seu método desenhar
FormaGeometrica forma1 = fabrica.criarForma(TipoForma.CIRCULO);
if (forma1 != null) {
forma1.desenhar();
}
// Obtém um objeto Retângulo e invoca seu método desenhar
FormaGeometrica forma2 = fabrica.criarForma(TipoForma.RETANGULO);
if (forma2 != null) {
forma2.desenhar();
}
// Obtém um objeto Quadrado e invoca seu método desenhar
FormaGeometrica forma3 = fabrica.criarForma(TipoForma.QUADRADO);
if (forma3 != null) {
forma3.desenhar();
}
}
}
6. Saída do Programa
Ao executar o programa TesteFabrica, a saída esperada será:
Desenhando um Círculo.
Desenhando um Retângulo.
Desenhando um Quadrado.