Olá Pessoal,
Dando continuidade a série “Design Pattern não é receita de bolo” hoje vamos conhecer o padrão Factory Method que pertence aos Padrões de Criação. Para quem não acompanhou o ultimo post, conhecemos como Design Pattern é dividido, saber qual padrão usar é tão importante quanto saber que tipo de padrão ele pertence.
Lets go…
Download do Projeto
Os projetos que serão usados no post estão no meu GitHub:
https://github.com/camilolopes/workspacedesignpattern
note: Sei que para alguns nem merecia colocar esse note por ser obvio, mas por medida de segurança achei bom não faltar. Esqueça tradução ao querer estudar Design Pattern. É em inglês o nome do padrão e evite ao máximo de traduzir. O motivo, que a tradução atrapalha mais do que ajuda.
Factory Method (Padrão de Criação)
Encapsula a escolha da classe concreta na criação de objetos de um TIPO.Ou seja, é quando tenho várias “respostas” com base no TIPO passado.
Exemplo:
se o cliente é JURIDICO(do tipo juridico) envio uma mensagem com o CNPJ;
se o cliente é FISICO(do tipo fisico) envio uma mensagem com CPF;
Esse padrão normalmente tem um intermediario que ajuda decidir qual implementacao da classe concreta será chamada com base no tipo da chamada.
Esqueleto do padrão
- Product: é interface que define o objeto a ser criado
- ConcreteProduct: implementação particular do tipo do objeto a ser criado
- Creator: classe/interface que define a assinatura do metodo responsavel pela criacao do produto
- concreteCreator: classe que implementa ou sobrescreve o método de criação do produto.
Na prática
Veremos agora na pratica ele em ação, crie um projeto seu Eclipse e chame como achar melhor, aqui chamei de FactoryMethodPessoa com essa estrutura:
Deixe seu projeto conforme a imagem acima. A seguir vamos meter mão na massa.
Desenvolvendo
Chegou a hora de desenvolver. O ponto mais importante ao querer entender como usar o design pattern X ou Y. É entender o objetivo dele. Na maioria das vezes, um design pattern sempre começa por uma interface para manter o código orientado a interfaces. Mas, isso não é regra. Vou fazer um desenvolvimento orientado a questionamento e alinhamento como tipo de design pattern. Foi assim que fiz para buscar entender e saber usar na hora “H”. Eu particularmente não tive ainda a felicidade de usar todos os design pattern em uma “solução cliente”, como prova de conceito é sempre mais simples. Mas, em regra de negócio as coisas são mais dinâmicas. Vamos largar de papo e mão na massa.
Passo 1
Eu me pergunto: o que minha aplicação pode fazer? Enviar mensagens para diferentes tipos de pessoa.
Então é bom eu ter uma interface onde cada pessoa diz como quer ser chamada.
package br.com.camilolopes.interfaces;
/* Product */
public interface Envio {
void enviar(String mensagem);
}
Posso dizer que o serviço de envio de mensagens é um produto? Sim. E que será implementado com base no tipo de mensagem que deseja enviar.
Passo 2
Agora precisamos definir que tipo de mensagens vamos poder enviar. No nosso exemplo, podemos mandar mensagens com o CPF ou CNPJ, porém isso depende do tipo do cliente. Não posso mandar CPF para um cliente que é pessoa Juridica. A seguir temos a implementação da maneira mais simples, possível o objetivo é entender o design pattern.
PessoaFisica.java
package br.com.camilolopes.classes.impl;
import br.com.camilolopes.interfaces.Envio;
/*
* concreteProduct
*/
public class PessoaFisica implements Envio {
@Override
public void enviar(String mensagem) {
System.out.println(mensagem + “CPF “);
}}
PessoaJuridica.java
package br.com.camilolopes.classes.impl;
import br.com.camilolopes.interfaces.Envio;
/*
* ConcreteProduct
*/
public class PessoaJuridica implements Envio {
@Override
public void enviar(String mensagem) {
System.out.println(mensagem + “CNPJ”);
}}
Observe que para cada PessoaXX passamos uma mensagem + uma informação privada que cada um tem. Que nesse caso seria o CPF e CNPJ. Pense assim à nivel de negócio:
“Se eu estou me comunicando com Pessoa Juridica as minhas negociações, contrato serão feitas em cima de um CNPJ, é ele que é importante na relação.” Então nesse contexto se eu quiser mandar uma mensagem e no final envio o cnpj para confirmação.
Exemplo:
“Houve uma alteração de contrato para o CNPJ xxx”
Passo 3
Já temos as implementações do produto. Agora que vem a parter do Factory Method, onde precisamos chamar cada implementação com base no tipo. Algo assim:
Se for PessoaFisica
chame new PessoaFisica().enviar
senão se for PessoaJuridica
chame new PessoaJuridica().enviar
ifs encadeados nunca é uma boa opção, afeta a legibilidade e manutenção do código. Sem falar que amanhã pode nascer um novo tipo de PessoaXXX e teremos que aumentar mais ainda essa condição if/else. E veremos a seguir que conseguirmos evitar isso através do padrão factory method.
Criaremos uma classe que retorna o tipo do objeto com base no que foi passado para ele. E com esse retorno, chamaremos uma das implementações da nossa interface (Product). Veja:
package br.com.camilolopes.creator;
import br.com.camilolopes.interfaces.Envio;
/*
* classe que vai retornar o metodo implementado
* com base no tipo que recebeu
*/
public class CreatorEnvio {
//o segredo que evita N if/else encadeados \o/
public Envio getEnvio(Envio envio){
if (envio==null) {
throw new NullPointerException();
}
return envio;
}
}
Observe o segredo todo está aqui. Estamos com o código orientado à interfaces. O nosso método espera qualquer objeto que seja do tipo Envio, ou seja, qualquer implementação da interface. Verificamos se é null para não acontecer NullPointerException ao chamar o método e em seguida retornamos o objeto que recebemos.
E agora?
Simples. A classe principal que envia as mensagens, chamará CreatorEnvio passando para o tipo de pessoa que ele deseja enviar a mensagem e que mensagem será enviada. Veja:
package br.com.camilolopes.main;
import br.com.camilolopes.classes.impl.PessoaFisica;
import br.com.camilolopes.classes.impl.PessoaJuridica;
import br.com.camilolopes.creator.CreatorEnvio;
import br.com.camilolopes.interfaces.Envio;public class MainPessoa {
public static void main(String[] args) {
CreatorEnvio creator = new CreatorEnvio();//enviando mensagem para pessoa fisica
Envio envio = creator.getEnvio(new PessoaFisica());
envio.enviar(“caro cliente inscrito no “);//enviando para pessoa juridica
envio = creator.getEnvio(new PessoaJuridica());
envio.enviar(“Prezada Empresa inscrita no “);
}
}
Observe que é muito simples, todo código orientado à interfaces. Se amanhã nascer um novo tipo de Pessoa, não precisamos nos preocupar, apenas passaremos isso para o método getEnvio e ele chamará a implementação de enviar(mensagem) daquele novo tipo de PessoaXX.
O resultado
Eu gosto de criar unit tests, a seguir tem o unit test que fiz para validar. Com unit test não precisamos ter uma classe com o método main para saber se o nosso código está funcionando. Veja:
package br.com.camilolopes.creator;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import br.com.camilolopes.classes.impl.PessoaFisica;
import br.com.camilolopes.classes.impl.PessoaJuridica;
import br.com.camilolopes.creator.CreatorEnvio;public class CreatorEnvioTest {
private CreatorEnvio creatorEnvio;
@Before
public void setUp() throws Exception {
creatorEnvio = new CreatorEnvio();
}@Test
public void testGetPessoaFisica() {
assertTrue(creatorEnvio.getEnvio(new PessoaFisica()) instanceof PessoaFisica);
}@Test
public void testGetPessoaJuridica() {
assertTrue(creatorEnvio.getEnvio(new PessoaJuridica()) instanceof PessoaJuridica);
}@Test(expected = NullPointerException.class)
public void testNullIsInvalid() {
creatorEnvio.getEnvio(null);
}
}
Criei apenas um unit test para a classe CreatorEnvio e verifiquei se ele estava retornando o tipo do objeto correto com base no que foi passado para o método getEnvio.
Git Hub
Os projetos encontram-se no Github:
https://camilolopes@github.com/camilolopes/workspacedesignpattern.git
Vou ficando por aqui espero que tenham gostado do post. Apesar de ter ficado grande devido a parte pratica hehe.
Abracos, see ya