Modelo rico ou gambiarra?

27 respostas
G

Pessoal,

sempre que construo um sistema de login, no meu controller LoginUser(por exemplo) eu acabo fazendo o seguinte:

if(User.getPerfil.equalsIgnoreCase("administrador"))
     return "administrador.html";
if(User.getPerfil.equalsIgnoreCase("convidado"))
     return "convidado.html";
if(User.getPerfil.equalsIgnoreCase("cliente"))
     return "cliente.html";

uma forma que pensei para contornar esses ifs seria criar uma interface para usuário e cada perfil de usuário saberia para que página retornar, ficaria mais ou menos assim:

public interface IUsuario {
    public String logar();
}

as implementações

public class Cliente implements IUsuario {
    private int id;
    private String nome;
   
    @Override
    public String logging() {
        return "cliente.html";

    }

}

public class Administrador implements IUsuario {
    private int id;
    private String nome;
   
    @Override
    public String logging() {
        return "administrador.html";
    }

}

dessa forma eu até evito de ter o atributo perfil, pq a própria classe é o perfil. e assim no meu controller eu teria algo como:

return user.logging(); //não precisando mais dos ifs.

só que fazendo isso eu estou colocando a regra de login dentro do meu modelo, o próprio objeto administrador/Cliente saberia como fazer o login.
aí vem minha dúvida, isso é modelo rico ou gambiarra?

27 Respostas

ViniGodoy

Essa é a famosa refatoração: Substituir if/switch por polimorfismo.

Outra opção seria usar um map:

public Map<String, String> telas = new HashMap<String, String>(); telas.put("administrador", "administrador.html"); telas.put("convidado", "convidado.html"); telas.put("cliente", "cliente.html");

E na hora h…

return telas.get(User.getPerfil().toLowerCase());
A

Não acho que seja gambiarra.

Mas o que eu faria é criar uma classe para o atributo Perfil, ou mesmo uma enum.

Nesta classe/enum teria o método para retornar o html correto.

Hebert_Coelho

Como já até falaram, eu prefiro por enum viu. Algo parecido como abaixo:

public enum Perfil{
    ADMIN("admin.xhtml"), USER("user.xhtml")

    private String page;

    private Perfil(String perfil){
        this.page = page;
    }

    public String getPage(){
        return page;
    }
}
G

só que fazendo isso eu estou colocando a regra de login dentro do meu modelo

e sobre isso? não fica estranho isso?

Hebert_Coelho

gambazinho:
só que fazendo isso eu estou colocando a regra de login dentro do meu modelo

e sobre isso? não fica estranho isso?


Para fugir disso então, você poderia no caso do enum, criar: um arquivo de constantes de páginas, ou um resource bundle ou um arquivo de configurações e salvar apenas a chave da página no enum.

Tipo assim:
ADMIN(“PAGINA_ADMIN”), USER(“PAGINA_USER”)
e quando você fosse utilizar esse valor, você buscaria no real da página.

Desse modo, seu modelo sabe apenas que o ADMIN teria uma PAGINA_ADMIN mas não terá conhecimento de qual página é.

Que tal?

FernandoFranzini

Vc esta correto sim!
Prefira estrutura polimórficas sobre controle de IF’s.
No caso vc optou bem, implementando o padrão STRATEGY.
Unico erro é que vc furou a camada, colocando a pagina dentro do modelo.
A regra de negocio deve ser independente de infra estrutura e detalhes de front-end.

G

FernandoFranzini:

Unico erro é que vc furou a camada, colocando a pagina dentro do modelo.
A regra de negocio deve ser independente de infra estrutura e detalhes de front-end.

então é essa minha dúvida, como corrigir isso sem ter de criar enum??

Hebert_Coelho

gambazinho:
FernandoFranzini:

Unico erro é que vc furou a camada, colocando a pagina dentro do modelo.
A regra de negocio deve ser independente de infra estrutura e detalhes de front-end.

então é essa minha dúvida, como corrigir isso sem ter de criar enum??


Pq não criar enum?
O enum abaixo não vai ter detalhe algum do front-end. Em que a solução abaixo fura o modelo?

jakefrog:
Para fugir disso então, você poderia no caso do enum, criar: um arquivo de constantes de páginas, ou um resource bundle ou um arquivo de configurações e salvar apenas a chave da página no enum.

Tipo assim:
ADMIN(“PAGINA_ADMIN”), USER(“PAGINA_USER”)
e quando você fosse utilizar esse valor, você buscaria no real da página.

Desse modo, seu modelo sabe apenas que o ADMIN teria uma PAGINA_ADMIN mas não terá conhecimento de qual página é.

Que tal?

G

jakefrog poderia dar um pequeno exemplo de como isso ficaria? em que pacote eu criaria esse enum?

Hebert_Coelho
gambazinho:
jakefrog poderia dar um pequeno exemplo de como isso ficaria? em que pacote eu criaria esse enum?
Você criaria dentro do mesmo pacote do seu usuário, pois faz parte do modelo saber qual o perfil do seu usuário. Quando você fosse buscar a página, você buscar em um arquivo properties algo como abaixo:
public String getPage(){
    return properties.get(user.getPerfil().getPage());
}
Perfil, seria o Enum. Note, que o perfil vai indicar o tipo/chave (admin.page) da página mas não a página em si (/pages/admin.xhtml). Em seu arquivo de properties você teria algo do tipo:
pagina.admin=/pages/admin.xhtml
pagina.user=/pages/user.xhtml
E seu enum seria tipo:
public enum Perfil{  
    ADMIN("pagina.admin"), USER("pagina.user")  
  
    private String page;  
  
    private Perfil(String perfil){  
        this.page = page;  
    }  
  
    public String getPage(){  
        return page;  
    }  
}
FernandoFranzini
  1. O controle de decisão do front-end deve ficar na camada de visão utilizada - web, desktop, celular, smartphone etc.
  2. O tipo polimórfico do objeto de domínio ja indica isso, vc não precisa misturar o strategy + template method.
Na camada de visão:

if (objetoDominio instanceof X) {

return enviar para front-end A;

} else if (objetoDominio instanceof Y) {

return enviar para front-end B;

{

Tipico metodo de camada front-end MVC.

G

FernandoFranzini:
1) O controle de decisão do front-end deve ficar na camada de visão utilizada - web, desktop, celular, smartphone etc.
2) O tipo polimórfico do objeto de domínio ja indica isso, vc não precisa misturar o strategy + template method.

Na camada de visão:

if (objetoDominio instanceof X) {

return enviar para front-end A;

} else if (objetoDominio instanceof Y) {

return enviar para front-end B;

{

Tipico metodo de camada front-end MVC.

mas aí eu estaria apenas trocando um if por outro… o problema dos ifs continuariam da mesma forma…
concorda?

if (objetoDominio instanceof X) {
return enviar para front-end A;
} else if (objetoDominio instanceof Y) {
return enviar para front-end B;
{

e

if(User.getPerfil.equalsIgnoreCase("administrador"))  
        return "administrador.html";  
   if(User.getPerfil.equalsIgnoreCase("convidado"))  
        return "convidado.html";  
   if(User.getPerfil.equalsIgnoreCase("cliente"))  
        return "cliente.html";
FernandoFranzini

mas aí eu estaria apenas trocando um if por outro… o problema dos ifs continuariam da mesma forma…
concorda?

Sim…mas agora esta:

  1. flexível
  2. Extensível
  3. Na camada correta.

Veja que a cada vez que vc criar uma classe nova de sua API q tenha regras de autenticação intercambial, vc precisa manutenir esse if:

  1. criar uma classe novo, herdando da base.
  2. criar um html novo.
  3. aumentar o if.

Seu estrategia arquitetural esta 100% compatível.

Qual o problema?

G

FernandoFranzini:

3) aumentar o if.
Qual o problema?

esse é o problema…

FernandoFranzini

Eu não acho ruim não…acho que esta consistente.
A questão é - que adianta vc fazer um estrutura mais genérica, sem aumentar o if sendo que vc tem que criar mais uma pagina para cada estrategia?
Outro fator?
Qual é previsão desse tipo de caso de uso mudar?
normalmente vc vai criar um grupo agora no inicio e ficar estável para muito tempo.
Desde que vc documente no GUIDELINE arquitetural ta consistente sim.

A

Com todo respeito FernandoFranzini, discordo da sua abordagem.

Você afirma que o sistema está mais flexível, extensível e na camada correta.
Eu pergunto: Onde a solução do jakefrog falha neste aspecto?

Pela sua arquitetura, a cada novo perfil você teria que alterar em 3 pontos, na dele apenas em 2.
Uma vantagem adicional: você seria obrigado a implementar o método.
Um if é muito mais simples de se esquecer.

Como disse o gambazinho, você está trocando um if simples por um complexo.
Não vejo vantagens nisso.

Aliás, pensando melhor no código inicial postado, com uma simples convenção você trocaria o if por uma linha, no controller:

return User.getPerfil() +  ".html";

Por ser uma página diferente por perfil, a criação do html em si é inevitável.
O que precisamos minimizar é o número de lugares que são afetados a cada mudança (isso facilita a manuntenção).

Em todos os outros pontos acredito que a duplicação é desnecessária.

FernandoFranzini

É isso que ta matando a decisão…
adicionar uma pagina + 1 if não faz diferença…kkkkk Se existe manutenção…existe!
Eu particularmente, na verdade faria de tudo para não adicionar pagina a cada no perfil de login, deixando tudo flexível…Ou seja, uma unica pagina por perfil. Ou seja, mudança de filosofia…
Mesmo usando enum, ele teria que adicionar mais um valor nas constantes ao inves do if…ou seja…ficariamos na mesma…
Mas veja que o cenário do amigo ai…

A

O if extra faz diferença justamente na hora da manuntenção, é muito simples esquecer.
Se você pode resolver com menos pontos de alteração, para que resolver com mais?

O uso de enum pelo menos te forçaria a passar algum valor para o getPage() , evitando que esqueça de implementar esta parte.

Se você tiver o sistema com views em desktop, mobile, web, etc… terá que ter um if em cada view.
Poderia se resumir a uma linha de código por view realizando o mapeamento.

FernandoFranzini

Se ambos não forem alterados o sistema não vai funcionar…ou seja, não tem como esquecer…
O esforço é o mesmo…kkkkkkk

Hebert_Coelho

Sim, mas se o if precisar ser utilizado em algum outro lugar… Aí vai ter que duplicar, duplica é feio, teria que criar uma classe para ter o método dos ifs expostos…

Desculpe, mas ainda prefiro o enum.

Como sempre, isso é gosto. Cabe ao dono do post escolher o melhor e ir fundo. [=

G

aparentemente um problema sem solução. no grupo de do jsf também não souberam me dar uma resposta.

Hebert_Coelho

gambazinho:
aparentemente um problema sem solução. no grupo de do jsf também não souberam me dar uma resposta.
Acostume-se a isso.
Quando se trata de padrão de projeto/OO vai ser raro os momento que você irá encontrar uma resposta definitiva.

Eu ainda penso, em minha opinião de quem ainda está aprendendo muito, que você deveria realmente utilizar o Enum.

Com ele você não irá precisar fazer ifs, e nem irá salvar a página no modelo. [=

FernandoFranzini

Claro que não tem solução…é problema de filosofia e não de código…
Vc ta alterando o caso de uso da solução…não exite magica que faça vc mudar o requisito da solução e a não tenha que fazer nada pq o sistema esta generico e flexível ao ponto de engloba tal mudança sem manutenção.

A questão é q vc mesmo se pré-formatou a criar uma classe a cada nova filosofia de autenticação…por que vc fez isso?
não teria como vc mudar a filosofia?

  • Não importa quantos diferente usuários sejem adicionados, cria uma abstração que encapsule isso genericamente e faça 1 pagina unica de autenticação para todos!
  • Use configurações de soft-code para parametrizar isso.
  • Use classes com campos dinâmicos usando MAP.
  • Etc e por ai…
G

com todo respeito FernandoFranzini,

não estou esperando mágica alguma, apenas estou tentando resolver o problema que vc mesmo apontou e que eu já desconfiava:

compreende porque não queria usar enum?

W

Eu acho gambi.

E se o seu usuario eh esperto o bastante para descrobrir que a interface de administracao eh http://seu_site/administrador.html ???

Fora que eu nao gosto nada que contenha “magic strings”.

//Daniel

G

desculpe mas não consegui ver problema no fato do usuário ter conhecimento de que existe uma página administrador.html?
o problema é segurança de acesso, é isso? um Filter resolveria esse problema.

FernandoFranzini

Ola gambazinho

Seu caso esta claro…ambas opções terão manutenções…escolha uma delas e bola para frente.

Criado 25 de janeiro de 2012
Ultima resposta 31 de jan. de 2012
Respostas 27
Participantes 6