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...)
Oi marcone, ótimo post! realmente é necessário eliminar os ifs, veja minha resposta no guj. Forte abraço!!
ResponderExcluirmuito bom cara. vlw
ResponderExcluirSimplesmente, fantástico, a sua explicação! excelente didática, inclusive vou utilizar isto nas minhas aulas!
ResponderExcluirMuito bom o texto, Marcone. Gostei muito da maneira como abordou o tópico. Meus parabéns!
ResponderExcluirAdemir, Reuel, Cleyton e Marcelo: obrigado pelos comentários, fico feliz em saber que estão acompanhando o blog, e o retorno de vocês é a motivação necessária para continuar.
ResponderExcluirEm breve vou postar 2 novos tópicos: "A solução para todos os problemas" sobre design patterns, e mais uma "Pegadinha Java" sobre dar new em Interface. Espero que gostem.
Voltem sempre e conto com seus comentários! Abraço.
Marcone,
ResponderExcluirParabéns pelo blog e pelo post!
Que didática! Como ficou fácil entender o assunto, sendo tratado desta forma!
Obrigado!
Abraço,
Já peguei um sistema assim... tinha mais de 100 IFs! JURO! Mudei "geral" com o uso de interfaces.
ResponderExcluirAcredito que tenha sido um sitema legado, pode me ensinar a aplicar isso em sitemas legados sem quebrar tudo e não sair do "padrão" ?
Excluir.:N:.
ResponderExcluir________
Rapaz... muito bom o exemplo, parabens... estou a um bom tempo aq na net procurando uns exemplos interessantes do padrão Strategy e finalmente encontrei um sussa... valeu :) Espero por mais posts hehe.
See ya!
________
.:N:.
Adicionado aos favoritos
ResponderExcluirParabéns!
ResponderExcluirCara... eu to no 5º período de Sistemas de Informação e tem muita coisa sobre Análise/Projeto OO que eu tenho um pouco de dificuldade de assimilar. 1) Por que eu não estudo o suficiente; 2) Por não achar textos interessantes assim para me animar a ler mais sobre o assunto.
ResponderExcluirProgramação, então, nem se fala. Só esse ano eu tomei vergonha na cara e comecei a dar uma atenção maior ao assunto.
Vou acompanhar seu blog, por que aqui eu encontrei o que precisva o/
Valeu mesmo.
Abraços
Nossa muito bom post, fiz um projetinho aqui no trampo com uns 20 ifs, vou alterar todos.
ResponderExcluirObrigado Marcone por compartilhar essa ideia
Parabéns pelo texto.
ResponderExcluirEste comentário foi removido pelo autor.
ResponderExcluirAmigos, sei que o tópico diz "JAVA" porem não entendo muito da sintax dessa linguagem, teria a possibilidade de mostrar este exemplo utilizando C#?
ResponderExcluirOutra coisa e se nesse meio eu tivesse uma classe PessoaTipo com IDPessoaTipo e descricao(fisica ou juridica) como ficaria?(isso se essa situação é viavel claro)
Vlw
Olá, amigo, obrigado pela sua participação!
ExcluirEm relação à sintaxe, infelizmente eu não vou poder te ajudar, pois não programo em C#. No livro da GoF você encontra exemplos em C++ e Smalltalk, talvez sejam mais pertinentes pra você.
Em sua segunda dúvida, você apresentou um cenário bastante diferente! Eu criei o cenário apresentado apenas para demonstrar como se aplica o Strategy, mas é claro que este padrão pode e deve ser utilizado em inúmeros cenários diferentes! Neste ponto eu indicaria, mais uma vez, uma leitura ao livro da GoF; é um excelente livro! Lá você poderá encontrar mais exemplos, em uma discussão mais aprofundada.
Obrigado novamente pela sua participação,
bons estudos!
Abraço,
Marcone.
JHOL, veja se este link pode te ajudar (contém exemplos em C#)
Excluirhttp://www.dofactory.com/net/strategy-design-pattern
att,
Marcone
Teoricamente, entendi sua explicação, mas ainda não consigo aplicá-la na pratica. Gostaria de saber como ficaria o código a seguir sem if:
ResponderExcluirString audio = "";
txtQtdArquivos.setText(Integer.toString(ar.lisArq.getSize()));
if (ar.lisArq.getSize() >= 2) {
int item1 = lstArquivos.getSelectedIndex();
int item2 = item1 + 1;
audio = ar.Audio(ar.lisArq.get(item1).toString(), ar.lisArq.get(item2).toString());
} else if (ar.lisArq.getSize() == 1) {
audio = "DUBLADO";
} else if (ar.lisArq.getSize() == lstArquivos.getSelectedIndex()+1) {
audio = "DUBLADO";
}
Oi Anderson, obrigado pela sua participação!
ExcluirBasicamente, a ideia é você pegar os valores testados nos seus ifs e colocar como chave em um HashMap; com isso, você delega ao HashMap a responsabilidade de te retornar uma nova instância de uma classe; esta classe terá um método que será responsável por fazer as operações dentro do seu if.
Por exemplo, você tem o seguinte código:
if (ar.lisArq.getSize() == 1) {
audio = "DUBLADO";
}
Logo, este valor "1" vai ser uma chave no seu mapa, e ele deverá devolver uma nova instância de uma classe (vamos chamá-la de ClasseUm; por sua vez, ClasseUm implementa a interface ClassePai); esta ClasseUm implementa o método da interface, que vai fazer a atribuição audio="dublado", assim:
Map mapa = new HashMap();
mapa.put("1", new ClasseUm());
Desta forma, eu não preciso testar com if qual classe utilizar, o mapa faz isso pra você! Utilizando o seu exemplo, eu faria:
ClassePai classe = mapa.get(ar.lisArq.getSize());
classe.executaMetodo();
Quando o valor de ar.lisArq.getSize() for igual a 1, o mapa vai te retornar uma instância da ClasseUm; e ao chamar o método "executaMetodo", o que estaria implementado nele? O que você precisa quando o valor for igual a 1, ou seja:
public void executaMetodo(){
audio = "DUBLADO";
// ou então
setAudio("DUBLADO");
// etc...
}
Este exemplo foi apenas pra você captar a ideia; geralmente, se os seus ifs apenas testam valores, e isso não vai ser alterado nem com mudança de requisitos, talvez não seja o caso de aplicar o padrão... Teríamos que ver com calma seus requisitos, o que certamente seria bastante complicado fazê-lo por aqui... Mas, enfim, espero ter passado a ideia de como este padrão funciona. Em caso de dúvidas, é só mandar outra mensagem, estou à disposição.
Grande abraço,
Marcone
Sério que vc acha mais eficiente instânciar 2 objetos em memória, dentro de um terceiro objeto que é o HashMap, à colocar um if else? Ou seja, entupimos a memória, acabamos com a máquina e seus recursos, mas o código ficou "bonito e mais fácil de ler"?
ResponderExcluirNão se trata de mais bonito, se trata de mais fácil manutenção. Você leu o post inteiro?
ExcluirDesculpem ressuscitar o post, mas a questão aqui é que em sistemas de grande porte, com muitas regras e funcionalidades que se alteram a todo instante, o padrão aqui apresentado faz todo sentido. Programar um sistema deste tipo com if/else é absolutamente inviável. E não há diferença no consumo de memória, pois estamos instanciando diversos objetos pequenos ao invés de instanciar um único objeto grande.
ExcluirMuito bom Marcone, me ajudou a entender melhor o padrão!
ResponderExcluirMuito boa a maneira que explica o assunto, irá me ajudar muito, irei parti dai para tirar uma redação sobre o assunto para um trabalho.
ResponderExcluirMuito Obrigado!!!
2023 e esse post nunca morre, parabéns, eu gostaria de pedir sua permissão para reproduzir este artigo na plataforma Medium, dando os devidos créditos.
ResponderExcluir