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
Show de bola o post Camilo…
Começou explicando sobre as partes do DP e fechou com chave de ouro implementando os testes unitários.
Abraços
opa! Altieres,
obrigado hehe. Coloquei unit test em alguns, porém veremos q outros posts não teremos. O motivo que alguns leitores talvez não conheça unit test e sente mais confortável em testar com o main, então vou tentar fazer a série para agradar a cada um. :).
abracos, obrigado por deixar seu feedback.
Companheiro, que maravilha de post foi esse!!!
Tranquilíssimo de entender.
Sua série sobre Design Pattern tem me ajudado demais.
Muito obrigado pelo esforço em escrever isso.
=D
Camilo, com certeza o uso de diagrama de classe, para ajudar no entendimento do padrão discutido, vai ajudar e muito. Ótima série! =)
opa!Luiz,
Realmente, eu acho que fiz, mas na hora parece que esqueci de colocar hehe vou olhar nos meus docs.
abracos,
Olá, muito boa explicação, mas só uma dúvida, de acordo com o padrão Factory method, está correto aquela instanciação de um novo objeto (Pessoa física e jurídica) na classe main?
olá Juliano,
O que há de errado na instanciação? Lembrando que usei o método main, apenas como meio de mostrar o resultado, mas este não tem relação alguma com o design pattern em questão.
flw.
Olá, acho que acabei me confundindo em alguma parte deste método.Acreditava que a classe concreta de um produto retornaria o objeto, dependendo do parâmetro transmitido a classe criadora.Bom, de qualquer forma obrigado pela atenção.Abraços.