O título deste post se refere a uma boa prática em programação Orientada a Objetos. Nossa equipe de desenvolvimento já adotou este lema, e sempre que chega um colega novato, fazemos questão de apresentá-lo a esta lei: "É proibido utilizar IFs negociais", ou, melhor ainda: "IF BOM É IF MORTO".
Ao ouvir esta lei, a primeira pergunta óbvia que escutamos é: - "Se não posso utilizar IF, então vou utilizar o quê? Switch-Case?"
A resposta é: Não!
Na verdade, não devemos utilizar nem IFs, nem Switch-Case, nem qualquer outra estrutura condicional em nosso código!
Aqui, vamos chamar toda e qualquer estrutura de decisão - seja IF, Switch-Case, ou qualquer outra - apenas como IF pra facilitar.
E por quê não podemos utilizar IF em nosso código?
Pelo seguinte motivo: queremos nos beneficiar das inúmeras vantagens da Orientação a Objetos, e o IF nos remete à velha e ultrapassada programação estruturada. Aliás, o IF é a própria estruturação do código que desejamos evitar:
SE isso
FAÇA aquilo
SENÃO
FAÇA aquilo outro
SWITCH(numero)
CASO 1: faça aquilo
CASO 2: faça aquilo outro
etc etc
Ou seja, é a própria essência da estruturação, ou pelo menos, a característica mais engessada da programação estruturada! Ela demanda várias manutenções no código, sempre que um novo Caso for identificado. Ora, mas dentre outras coisas, não queremos utilizar a OO para minimizar o impacto das alterações?
Então, tome nota desta Lei: IF BOM É IF MORTO.
O questionamento que escutamos em seguida pelos programadores novatos é: - "Mas se não posso usar nem IF, nem Switch-Case, nem nada parecido, COMO é que vou tratar isso?"
É o que veremos a seguir. A regra do nosso exemplo diz que: o programa deverá receber do usuário uma informação "PF" ou "PJ", indicando se vai tratar de Pessoa Física ou Pessoa Jurídica.
SE for pessoa física, o sistema imprime na tela "Pessoa Física", consulta e grava dados num banco Oracle, envia um e-mail automático para a Empresa X, gera um LOG numa pasta específica, e imprime um Relatório de PF.
SE for pessoa jurídica, o sistema imprime na tela "Pessoa Jurídica", consulta e grava dados num banco DB2, envia 3 e-mails automáticos para a Empresa X, Y, Z, gera um LOG em outra pasta específica, e imprime 2 Relatórios de PJ.
Implementando da maneira estruturada, teríamos o seguinte:public class Executavel{
public static void main(String[] args){
String tipo = args[0];
if(tipo.equals("PF")){
System.out.println("Pessoa Física");
System.out.println("Consultando Oracle...");
}
if(tipo.equals("PJ")){
System.out.println("Pessoa Jurídica");
System.out.println("Consultando DB2...");
}
GeraMail.trataTipo(tipo);
TrataLog.grava(tipo);
Relatorio.imprime(tipo);
}
}
Um exemplo simples, que recebe o parâmetro do usuário na linha de comando, sem muito tratamento, apenas para ilustrar os malefícios da abordagem estruturada com IFs.
O "arquiteto" que projetou a solução acima, baseou toda a solução nos IFs. Note que, na chamada a GeraMail.trata(tipo), ele precisa enviar a informação do usuário (PF ou PJ), e dentro desta classe vamos ter outra estrutura de IFs para tratar o envio de e-mail. O mesmo acontece com TrataLog e Relatorio, replicando a estrutura de IFs por diversas partes do código.
Ao projetar a "brilhante" solução acima, o "arquiteto genial" pensou: - "Bem, os 2 únicos tipos possíveis neste caso para pessoa é Física ou Jurídica. Então, vou fazer com IFs mesmo". Aí então, 4 meses de trabalho depois, com toda a "arquitetura" pronta e faltando 1 semana para o prazo final da entrega do projeto, ele recebe um telefonema do Cliente, que diz: - "Preciso tratar mais um tipo! Se refere à PJS - Pessoa Jurídica optante pelo Simples, que, no meu requisito, vai possuir regras completamente diferentes dos outros tipos".
Pânico! Desespero! Ele precisa alterar o método main() acima para inserir mais um tratamento, e precisa alterar a classe GeraMail para tratar mais um IF, e precisa dar manutenção também na classe TrataLog, e também na Relatorio, e em mais 7.586 classes do sistema... :-) [...geralmente é o que acontece na prática, além de ter que testar tudo novamente...] Ou seja, muitas noites sem dormir, trabalho no sábado e domingo, aumento enorme da probabilidade de falhas no sistema, e muito provavelmente, mais um projeto que vai estourar custo e prazo!
Tudo porque ele negligenciou o nosso lema: IF BOM É IF MORTO.
Como fazer sem usar IFs? Assim:
Vou criar a interface Pessoa: public interface Pessoa{
public void imprime();
}
Vou criar a classe PessoaFisica, que vai conter todos os tratamentos que são de responsabilidade de PF (gravar no Oracle, enviar e-mail para X, etc), e a classe PessoaJuridica, que vai tratar regras de PJ (grava no DB2, etc, etc). Ambas as classes vão implementar a interface Pessoa.
Apenas para que possamos rodar o nosso exemplo, e ver a solução funcionando na prática, vou fazer com que as classes PF e PJ exibam na tela suas informações, sem tratar acesso a banco nem como gerar logs, etc (não é assunto deste post).
A classe PessoaFisica.java ficaria assim:public class PessoaFisica implements Pessoa{
public void imprime(){
System.out.println("Pessoa Física");
System.out.println("Consultando Oracle...");
}
}
A classe PessoaJuridica.java ficaria assim:public class PessoaJuridica implements Pessoa{
public void imprime(){
System.out.println("Pessoa Jurídica");
System.out.println("Consultando DB2...");
}
}
Agora, sem os malditos IFs, a mesma classe Executavel.java do início do exemplo:public class Executavel{
public static void main(String[] args){
Map mapaDePessoas = new HashMap();
mapaDePessoas.put("PF", new PessoaFisica());
mapaDePessoas.put("PJ", new PessoaJuridica());
String tipo = args[0];
Pessoa pessoa = (Pessoa) mapaDePessoas.get(tipo);
pessoa.imprime();
}
}
Compile, rode e ateste a eficácia da solução:
/> java Executavel PF
Pessoa Física
Consultando Oracle...
/> java Executavel PJ
Pessoa Jurídica
Consultando DB2...
Pronto! De acordo com a informação dada pelo usuário, executei regras diferentes sem utilizar 1 único IF (nem Switch-Case...)
E se eu precisar de um novo tipo para Pessoa Jurídica optante pelo Simples - PJS? Aí então eu vou criar esta nova classe PJS, com suas regras específicas, e adicionar uma entrada no Mapa:
mapaDePessoas.put("PJS", new PJS());
E só! Nenhum outro ponto em todo o Projeto precisaria ser alterado!
Por fim, vale ressaltar que a desvantagem do IF é esta propagação de manutenções que ele demanda em caso de alteração. Entretanto, para utilizar o mecanismo da inicialização tardia, podemos fazer o trecho abaixo sem problemas:public Conexao getConexao(){
if(this.conexao == null){
this.conexao = new Conexao();
}
return this.conexao;
}
Você pode estar pensando: - "Mas você disse que é proibido utilizar IFs". Não, eu não disse isso!
O que eu disse foi: é proibido usar IFs negociais! Este não é um IF negocial, ele apenas testa o valor nulo. Ou, como diz a nossa Lei: "IF BOM É IF MORTO". Exatamente! Este IF acima está morto! Ou seja, não há a menor possibilidade de ele vir sofrer alterações futuramente. Na inicialização tardia, eu testo se o valor é nulo, e isto nunca vai ser alterado. O Cliente pode mudar todos os seus requisitos que, ainda assim, o método getConexao() acima vai continuar como está!
O exemplo aqui apresentado é uma implementação do GoF Pattern Strategy. As classes PessoaFisica e PessoaJuridica podem ser entendidas como estratégias que encapsulam seus algoritmos. Pretendo abordar o Strategy em outro post, específico sobre este padrão, para que possamos ver outros exemplos e discutir as abordagens do GoF. Até lá!
(*PS: não deixe de enviar seu comentário, dúvida, crítica, sugestões, discordâncias, perguntas, reclamações...)