Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
O design pattern Factory, ou conhecido também como Factory Pattern é um padrão de projeto criacional, sua principal utilização é disponibilizar uma interface para criação de objetos, mas que também permite que subclasses alterem o tipo de objetos que podem ser criados.
Devemos utilizar esse padrão quando você necessita de uma lógica complexa na criação de um objeto ou quando você precisa de definir o tipo de objeto com base em alguma informação como um parâmetro, por exemplo.
Usando Java é possível implementar esse padrão de diversas formas, mais a seguir vamos abordar uma das formas mais comuns, mas lembrando que esse é um campo aberto e você pode usar as diretrizes do padrão para personalizar e fazer com que ele atenda a sua regra de negócio.
Para exemplificar sua implementação vamos fazer um cenário bem simples, e com um exemplo bem comum, bom pelo menos pra mim quando aprendi orientação a objeto, sempre usavam o exemplos como animais para me explicar sobre herança.
Um ponto bem relevante é que para adotar esse tipo de padrão de projeto é necessário ter um certo domínio sobre a regra de negócio, para que você implemente ele da melhor forma.
Veja o exemplo a seguir, temos duas classes basicamente Cachorro e Gato e elas por sua vez são implementações de uma interface chamada Animal, bom até aí você já deve ter se lembrado dos exemplos.
public interface Animal {
void fazerSom();
}
Animal.javapublic class Cachorro implements Animal {
@Override
public void fazerSom() {
System.out.println("Latindo...");
}
}
Cachorro.javapublic class Gato implements Animal {
@Override
public void fazerSom() {
System.out.println("Miando...");
}
}
Gato.javaO próximo passo é criar uma fábrica que consiga criar cada um desses animais, bom geralmente usamos essa abordagem com criações de objeto com lógicas mais complexas, mas para o exemplo vamos seguir na simplicidade.
Primeiro vamos definir uma interface para que nossas fábricas sigam o padrão:
public interface AnimalFactory {
Animal criarAnimal();
}
AnimalFactory.javaNote que nossa fábrica possui apenas um método que cria um animal, e sempre trabalhamos com as interfaces para garantir um desacoplamento, agora vamos criar implementações dessa fábrica com sua regra específica.
Fábrica de cachorro:
public class CachorroFactory implements AnimalFactory {
@Override
public Animal criarAnimal() {
return new Cachorro();
}
}
CachorroFactory.javaFábrica de gato:
public class GatoFactory implements AnimalFactory {
@Override
public Animal criarAnimal() {
return new Gato();
}
}
GatoFactory.javaNote que nas implementações específicas precisamos apenas instanciar as classes das quais elas são responsáveis.
Agora vamos demonstrar como utilizar essas fábricas para a nossa implementação, primeiro exemplo vamos criar um cachorro.
public class Exemplo {
public static void main(String[] args) {
AnimalFactory factory = new CachorroFactory();
Animal cachorro = factory.criarAnimal();
cachorro.fazerSom();
}
}
Exemplo.javaViu como é simples a lógica de criação do animal acaba ficando encapsulada dentro da fábrica, agora vejamos como criar um gato.
public class Exemplo {
public static void main(String[] args) {
AnimalFactory factory = new GatoFactory();
Animal gato = factory.criarAnimal();
gato.fazerSom();
}
}
Exemplo.javaPodemos testar esse exemplo de uma forma melhor, recebendo um parâmetro que vai definir a criação de cada animal.
public class Exemplo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Digite o tipo de animal: ");
String tipo = scanner.nextLine().toUpperCase().trim();
AnimalFactory animalFactory = null;
switch (tipo) {
case "CACHORRO":
animalFactory = new CachorroFactory();
break;
case "GATO":
animalFactory = new GatoFactory();
break;
}
Animal animal = animalFactory.criarAnimal();
animal.fazerSom();
scanner.close();
}
}
Exemplo.javaNote que em momento algum referenciamos alguma classe concreta, estamos trabalhando sempre com as abstrações, isso torna o nosso código muito poderoso, porque podemos acrescentar implementações sem ferir a lógica já criada e não corremos risco de desconstruir de alguma forma as implementações que já foram feitas.
Bom falando assim é bem simples pensar em uma maneira de aplicar, mas acho bem difícil cair para você um problema para criar classes baseadas em animais, então vamos tentar usar um problema um pouco mais plausível.
Digamos que você está trabalhando em um sistema que vai exigir pagamentos, e na atual situação, você está recebendo dois tipos de pagamento, cartão de crédito ou cartão de débito, vamos tentar utilizar esse padrão de projeto em um problema desse.
Como nosso próximo exemplo vamos imaginar que você está trabalhando em um sistema de pagamentos, que é capaz de receber pagamentos de diversos tipos diferentes, na situação atual precisamos implementar dois métodos, o de pagamentos via cartão de crédito e o de pagamento utilizando o PayPal, vamos tentar aplicar o mesmo padrão nessa solução.
Vamos criar uma interface comum que representará o nosso método de pagamento:
public interface MetodoPagamento {
void processarPagamento(BigDecimal valor);
}
MetodoPagamento.javaCada método de pagamento do nosso sistema deve implementar essa interface, veja como construímos:
Método de pagamento Cartão de crédito
public class CartaoCredito implements MetodoPagamento {
private String nomeTitular;
private String numero;
private String cvv;
public CartaoCredito(String nomeTitular, String numero, String cvv) {
super();
this.nomeTitular = nomeTitular;
this.numero = numero;
this.cvv = cvv;
}
@Override
public void processarPagamento(BigDecimal valor) {
System.out.println(String.format("Processando pagamento de %s com valor de %f", this.nomeTitular, valor));
}
}
CartaoCredito.javaMétodo de pagamento PayPal
public class PayPal implements MetodoPagamento {
private String usuario;
public PayPal(String usuario) {
super();
this.usuario = usuario;
}
@Override
public void processarPagamento(BigDecimal valor) {
System.out.println(String.format("Processando pagamento com PayPal para %s com valor %f", this.usuario, valor));
}
}
PayPal.javaAgora vamos implementar a abstração da nossa fábrica, todas as fábricas terão que seguir esse padrão:
public abstract class ProcessadorPagamento {
abstract MetodoPagamento criarMetodoPagamento(Map<String, Object> informacoes);
void executarPagamento(Map<String, Object> informacoes, BigDecimal valor) {
MetodoPagamento metodoPagamento = criarMetodoPagamento(informacoes);
metodoPagamento.processarPagamento(valor);
}
}
ProcessadorPagamento.javaNote que ao invés de usar uma interface como no exemplo anterior, utilizamos uma classe abstrata, isso porque de fato ela já executa um pouco de lógica no método de “executarPagamento”.
Agora precisamos implementar cada fábrica especializada em criar o seu método de pagamento:
Cartão de crédito
public class CartaoCreditoProcessadorPagamento extends ProcessadorPagamento {
private static final String CHAVE_NOME_TITULAR = "nomeTitular";
private static final String CHAVE_NUMERO = "numero";
private static final String CHAVE_CVV = "cvv";
@Override
MetodoPagamento criarMetodoPagamento(Map<String, Object> informacoes) {
String nomeTitular = (String) informacoes.get(CHAVE_NOME_TITULAR);
String numeroCartao = (String) informacoes.get(CHAVE_NUMERO);
String cvv = (String) informacoes.get(CHAVE_CVV);
return new CartaoCredito(nomeTitular, numeroCartao, cvv);
}
}
CartaoCreditoProcessadorPagamento.javaPayPal
public class PayPalProcessadorPagamento extends ProcessadorPagamento {
private static final String CHAVE_USUARIO = "usuario";
@Override
MetodoPagamento criarMetodoPagamento(Map<String, Object> informacoes) {
String usuario = (String) informacoes.get(CHAVE_USUARIO);
return new PayPal(usuario);
}
}
PayPalProcessadorPagamento.javaE aqui temos um ponto bem importante de observar, perceba que cada método de pagamento utiliza-se de informações diferentes, cada um tem o seu jeito próprio de ser construído, para abstrair isso utilizamos uma classe genérica do Java para transitar informações, pois cada fábrica saberá quais informações pegar.
Importante lembrar que essas informações poderiam trafegar dentro de uma classe criada também.
Para deixar nossa lógica mais organizada, também criamos um Enum classificando os tipos de métodos de pagamento.
public enum TipoPagamento {
CARTAO_CREDITO,
PAYPAL;
}
TipoPagamento.javaE por fim vamos implementar a classe que utilizará as fábricas e executará os métodos.
public class ExemploPagamento {
public static void main(String[] args) {
Map<String, Object> informacoesPagamento = new HashMap<String, Object>();
ProcessadorPagamento processadorPagamento = selecionarTipo(TipoPagamento.CARTAO_CREDITO);
BigDecimal valor = BigDecimal.valueOf(0.39);
MetodoPagamento metodoPagamento = processadorPagamento.criarMetodoPagamento(informacoesPagamento);
metodoPagamento.processarPagamento(valor);
}
private static ProcessadorPagamento selecionarTipo(TipoPagamento tipo) {
switch (tipo) {
case CARTAO_CREDITO:
return new CartaoCreditoProcessadorPagamento();
case PAYPAL:
return new PayPalProcessadorPagamento();
default:
throw new IllegalArgumentException("Método ainda não implementado");
}
}
}
ExemploPagamento.javaNo exemplo acima utilizamos o cartão de crédito e estamos passando as informações em uma variável comum, mas essas informações podem vir via parâmetros de uma chamada REST por exemplo.
Se observar bem, essa solução é muito escalável, para incrementar essa solução adicionando mais métodos é simples, e não impacta os que já foram implementados, as responsabilidades ficam completamente segregadas, e na hora de dar manutenção nesse tipo de implementação é bem simples e seguro já que cada implementação está de uma certa forma isolada uma da outra.