O Factory Method (ou Método de Fábrica) é um padrão de projeto do tipo criacional que oferece uma solução elegante para a criação de objetos em sistemas complexos.
Imagine que você está desenvolvendo um sistema para uma escola que precisa gerenciar alunos, professores e prestadores de serviço, cada um com atributos e comportamentos distintos. Sem um padrão adequado, a criação desses objetos pode se tornar confusa e enrolada como um prato de miojo frio, especialmente se a lógica de criação estiver espalhada por todo o código. O Factory Method visa resolver esses problemas, definindo uma interface para criar os objetos, promovendo desacoplamento, flexibilidade, facilidade de manutenção e adição de novos tipos de objetos no futuro.
Façamos um comparativo entre uma implementação inicial sem padrão definido e uma com o padrão Factory.
Sem padrão definido
Criei a classe que representa Aluno
public class Aluno {
String nome;
String sobrenome;
Long matricula;
}
Criei uma classe Professor
public class Professor {
String nome;
String Sobrenome;
BigDecimal salario;
}
A classe cliente, ao precisar instanciar Aluno e/ou professor precisa conhecer a implementação de cada uma...
public class ServicoPessoa {
Aluno aluno = new Aluno("Lucas", "Silva", 123456L);
Professor professor = new Professor("Maria", "Oliveira", new BigDecimal(5000));
///// Restante da implementação
}
Por enquanto é uma implementação padrão e comum, não representa riscos não é mesmo!?
Porém, ainda não criamos a classe para Prestadores de Serviço, que podem se dividir em várias outras como serviço de limpeza, manutenção de equipamentos e etc. A implementação traz repetição de código, acoplamento e riscos para o código.
Imaginemos que, a partir de agora todas as pessoas terão mais um atributo para e-mail. Já temos duas classes para refatorar. Imagine ainda que, teremos mais uma classe de funcionários, que terá os mesmos campos e mais atributos específicos. Olhando por este lado, começamos a encontrar potenciais problemas.
Pois bem, vamos refatorar este esqueleto e implementar o tal falado Factory.
Implementando Factory
Uma boa ideia seria criarmos um contrato com os atributos principais, fazendo com que eles não se repitam...
public abstract class Pessoa {
String nome;
String sobrenome;
String email;
public Pessoa(String nome, String sobrenome, String email) {
this.nome = nome;
this.sobrenome = sobrenome;
this.email = email;
}
public Pessoa() {
// Construtor padrão (sem argumentos)
}
A partir daqui, as outras classes extenderem a pessoa, herdarão os atributos comuns e declararão apenas os inerentes a cada uma.
public class Aluno extends Pessoa {
Long matricula;
public Aluno(String nome, String sobrenome, String email) {
super(nome, sobrenome, email); // Chama o construtor da classe Pessoa
}
public Aluno() {
// Construtor padrão (sem argumentos)
}
}
public class Professor extends Pessoa {
BigDecimal salario;
public Professor(String nome, String sobrenome, String email) {
super(nome, sobrenome, email); // Chama o construtor de Pessoa
}
public Professor() {
}
}
Com este primeiro passo já eliminamos a repetição de código e começamos a centralizar a lógica, mas podemos ir além..
Com esta refatoração, o cliente ainda precisa conhecer as classe Aluno e Professor para instancia-las. É agora que implementamos o padrão Factory!
Digamos que o nosso intuito inicial seja apenas criar um objeto de pessoa, para isso, iniciei criando uma interface de um factory central, que vai gerenciar a criação dos objetos.
public interface PessoaFactory {
Pessoa criarPessoa(String tipo, String nome, String sobrenome, String email);
}
Agora crio a implementação desta interface
public class PessoaFactoryManeger implements PessoaFactory {
@Override
public Pessoa criarPessoa(String tipo, String nome, String sobrenome, String email) {
if (tipo.equalsIgnoreCase("aluno")) {
return new Aluno(nome, sobrenome, email);
} else if (tipo.equalsIgnoreCase("professor")) {
return new Professor(nome, sobrenome, email);
}
return null;
}
}
Desta forma, as subclasses não precisam conhecer a implementação de Aluno nem Professor. Basta chamar a factory.
public class ServicoPessoa {
PessoaFactory factory = new PessoaFactory();
public void criarPessoas() {
Pessoa aluno = factory.criarPessoa("aluno", "João", "Silva", "joao.silva@email.com");
Pessoa professor = factory.criarPessoa("professor", "Maria", "Souza", "maria.souza@email.com");
//demais implementações, como inserir a nota do aluno, etc.
}
}
Este é um exemplo mais simples para entendermos a ideia, porém temos várias possibilidades, como:
- Criar métodos específicos em PessoaFactory, para criar um Professor, por exemplo. Ele já receberia o valor doa tributo salário. Exemplo:
public interface PessoaFactory {
Pessoa criarPessoa(String tipo, String nome, String sobrenome, String email);
Professor criarProfessor(String nome, String sobrenome, String email, BigDecimal salario);
Aluno criarAluno(String nome, String sobrenome, String email, Long matricula);
}
- Criar factory especifica para cada tipo de pessoa e delegar as responsabilidades para cada uma. Desta forma, a responsabilidade da PessoaFactoryManeger será apenas decidir qual outra factory chamar. Exemplo:
public class AlunoFactory implements PessoaFactory {
@Override
public Pessoa criarPessoa(String nome, String sobrenome, String email, Long matricula) {
return new Aluno(nome, sobrenome, email, matricula);
}
}
Estas são possibilidades que, apesar de serem mais trabalhosas na implementação inicial, diminuem muito a complexidade de manutenção, promovem desacoplamento, facilitam implementações de novas implementações de pessoas, proporcionam simplicidade em inserir novos atributos e comportamentos nas classes principais.
Vantagens do Factory Method:
Desacoplamento: desacoplou o código do cliente (que usa os objetos) das classes concretas que implementam esses objetos. O cliente interage apenas com a interface, sem precisar conhecer os detalhes de implementação das classes concretas. Isso deixa o código flexível e fácil de manter.
Flexibilidade: permitiu adicionar novas pessoas sem alterar o código "base". Desta forma fica muito mais fácil adaptar novas necessidades.
Encapsulamento: a utilização do padrão centralizou a lógica de criação dos objetos na fábrica. Facilitou a manutenção e evitou duplicação de código.
Responsabilidade única: cada fabrica foi se tornou responsável por criar um tipo especifico de objeto. Esse comportamento segue o principio de responsabilidade única, deixando o código mais modular e fácil de testar.
Extração de complexidade: caso haja complexidade na criação dos objetos, o cliente não precisa ter este conhecimento.
Desvantagens
Aumento da complexidade inicial: pode aumentar a complexidade inicial do código, principalmente se você tem muitos tipos de objetos para criar.
Procriação das classes: as classes podem se procriar como coelhos, principalmente se usarmos uma factory para cara objeto. Isso pode tornar o código mais difícil de navegar e entender.
Overkill / Exagero: em sistemas simples, o padrão Factory pode ser um exagero e aumentar complexidade ao invés de diminuir. Exemplo: se você tem apenas um ou poucos objetos, e não tem previsão de adicionar novos tipos no futuro.
Custo: embora o Factory facilite a manutenção a longo prazo, ele pode aumentar o custo inicial de manutenção, já que a criação das interfaces e classes de fábrica exigem tempo e esforço.
Considerações finais
Os padrões são poderosos e podem trazer muitos benefícios para os sistemas. No entanto, é importante avaliar as vantagens e desvantagens antes de usa-los. Em sistemas simples, o Factory Method pode ser um exagero, porém, em sistemas complexos ele pode ser um aliado valioso.