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.
Top comments (0)