Refactor para instanceof

11 respostas
paulog

Seguinte, tenho o seguinte código e gostaria de saber a melhor forma de realizar, se possível, a refatoração nele:

No projeto existem três pacotes:

jogo.core
jogo.animais
jogo.terrenos

O pacote jogo.animais contém as classes, por exemplo, de Rato e Leão, além da interface de IAnimal.
O pacote jogo.terrenos contém as classes, por exemplo, de Agua e Montanha, além da interface ITerreno.

No pacote jogo.core temos a classe Jogo.

O problema é o seguinte:
Na classe Jogo existe o método avaliaJogada que recebe um IAnimal e um ITerreno dentre outros parâmetros, para determinar se uma jogada é válida.

Um exemplo de jogada válida é quando o Rato deseja entrar na Agua. Nenhum outro IAnimal pode fazer isso. Então o meu código do método ficaria assim:

public boolean avaliaJogada(IAnimal animal, ITerreno terreno){
  
  if (terreno instanceof Agua){
   return animal instanceof Rato;
 }
}

Sem dúvida esses instanceof não ficaram bonitos.
Antes eu tinha feito que ITerreno possuía um método ehPropicio(IAnimal animal) que determinava se um animal poderia entrar na Agua.

O motivo da retirada de não continuar usando esse método na interface ITerreno é que, a priori, a classes do pacote jogo.terreno não deveriam saber nada sobre as classes jogo.animais.

Então, alguém tem alguma idéia de como poderia ser feito para ser retirado esses intanceofs? Algum design pattern?

11 Respostas

Alexandre_Bitencourt

Olá,

Acho sua solução do terreno mais elegante que o método avaliaJogada. Que tal inverter a lógica da solução para que o animal decida se o terreno é explorável ou não? Crie um método boolean isExploravel(ITerreno) na interface IAnimal. Fazendo isso você irá criar uma dependência de terreno em animal, o que é mais aceitável que criar a dependência de animal no terreno que você não queria criar.

Espero ter ajudado,

Alexandre Fidélis Vieira Bitencourt

paulog

Olá Alexandre,

Concordo com a sua opinião de que o método é muito mais elegante que o instanceof.

Mas dentro do método instanceof isExploravel(ITerreno) eu ainda teria um instanceof correto?

public class Leao{
  public boolean isExploravel(ITerreno terreno){
    // Leao nao pode entrar na água
    return !(terreno instanceof Agua);
  }
}

Enfim… ao meu ver, eu só mudei a localização do instanceof e ainda criei uma dependência do pacote animal para com o pacote terreno.

Não que essa solução esteja errada. Foi na primeira que eu pensei (mas ao contrário, com terreno validando o animal) e parece ser a melhor mesmo.

O problema mesmo para mim, é saber se aquele instanceof é válido.

sergiotaborda

A primeira coisa é realmente passar a logica para classe Animal. (Bom a primeira coisa é tirar o I do nome da interface…)

O fato de vc precisa de analizar a classe do terren o é porque vc tem diferentes tipos de terreno.
Se vc tem diferentes tipos de terreno eles deveria ter um atributo Tipo que poderiam ser um Enum.
Aliás, se a classe terreno não contém nenhuma logica deveria ser passado apenas o tipo de terreno, já que e´isso que determina se o animal pode ou não entrar nele.

paulog

Olá sergiotaborda,

Realmente, a lógica de qual terreno é válido ou não deveria ficar na classe Animal. Perfeito, já definimos esse ponto.

Agora em relação ao ITerreno (uso I inicial nas interfaces, porque gosto de seguir os padrões dos projetos do eclipse :stuck_out_tongue: ), não tem jeito. É um requisito do projeto que cada terreno tenha sua própria classe. Com Enum ficaria tudo mais simples, mas infelizmente não pode ser construído dessa forma.

O meu problema mesmo é, vamos dizer assim, fica “bonito” o código de Leao conter aquele instanceof para verificar se é o terreno Agua?

Se não, como posso alterá-lo?

T

que tal trocar seus instanceof por polimorfismo???

paulog

Então, de que forma eu faria nesse caso o uso do polimorfismo?

Alexandre_Bitencourt

Bom, primeiramente acho que o instanceof não chega a ser um problema pois é apenas uma comparação. Se não fosse feito com instanceof teria que ser feito de outra forma (usando um tipo enumerado ou constantes), o que acabaria dando na mesma só trocando o instanceof por um equals ou um ==.

Acho que no seu caso vale a pena analisar as dependências:

1o. caso: O Terreno avalia se o Animal pode ou não entrar nele (sua primeira proposta).

Acho que o Terreno não deve realmente precisar saber nada do Animal. Isso diminui seu acoplamento e aumenta sua coesão.
Portanto incluir o método isPropricio irá criar uma dependência indesejada. Além disso, a cada novo Animal adicionado, você terá que dar uma manutenção nos Terrenos que o aceitam para que o jogo funcione corretamente além de implementar no Animal o comportamento em cada Terreno.

2o. caso: O Jogo decide se o Animal pode entrar no Terreno (sua segunda proposta).

O Jogo irá possuir dependência com as interfaces de Animal e Terreno e com várias implementações que terão que ser comparadas com os instanceof. Acho que é uma solução ruim pois o número de dependências irá crescer a medida que novas classes forem sendo adicionadas. Imagine o tamanho deste método quando você tiver uns 10 tipos de animal e 10 tipos de terrenos?

3o. caso: O Animal decide se pode um não entrar no Terreno (minha sugestão).

O Animal de alguma maneira já deve conhecer o terreno pois seu comportamento deve ser diferenciado em cada terreno. Caso essa suposição seja falsa, as mesmas considerações do 1o. caso podem ser aplicadas. Supondo que isso seja verdadeiro, a introdução de um método isExploravel não aumenta o acoplamento pois ele já existia. Além disso, o método isExplorável só precisa conhecer as implementações de Terreno que o animal já conhece caso o animal não conheça aquele terreno o método irá retornar false. Acho essa opção a mais fácil de se manter pois ao criar um novo Animal não será necessário modificar nenhum Terreno e ao criar um novo Terreno apenas os Animais que o habitam teriam que ser modificados (Essa modificação já teria que ser feita pois o Animal tem um comportamento diferente neste Terreno novo).

Pelos motivos acima descritos prefiro a terceira opção.

Fico aguardando uma quarta opção do pessoal do fórum pois gostei muito da discussão :wink:

abração!

Alexandre Fidélis Vieira Bitencourt

paulog

Caro Alexandre,

Realmente o 3º caso é até agora a melhor resolução do problema.
Só estava mesmo com receios em relação ao intanceof, já que no meu ponto de vista, esse operador deve ser sempre usado com cuidado (salvo aqueles feitos antes de realizarem algum cast).

Nada ajuda mais do que uma segunda opinião e, por isso, agradeço a sua estimada ajuda Alexandre.

É claro que se alguém tiver alguma outras solução para esse problema, será muito bem vindo a discutir aqui no tópico.

sergiotaborda

paulog:

Agora em relação ao ITerreno (uso I inicial nas interfaces, porque gosto de seguir os padrões dos projetos do eclipse :stuck_out_tongue: ), não tem jeito. É um requisito do projeto que cada terreno tenha sua própria classe. Com Enum ficaria tudo mais simples, mas infelizmente não pode ser construído dessa forma.

O meu problema mesmo é, vamos dizer assim, fica “bonito” o código de Leao conter aquele instanceof para verificar se é o terreno Agua?

Se não, como posso alterá-lo?

Existe um problema fundamental com o seu modelo. Se vc está está estabelecendo regras conforme a classe ( o tipo) do objeto terreno vc não está seguindo um bom modelo OO. Tem que haver uma razão muito mais válida do que “é um requisito”. Vc pode ter uma classe Terreno e mesmo assim ter um método getType():TipoTerreno.
Acho que não ficou claro no outro post que o que proponho é que vc crie outra classe (enum) que seria retornada pelo terreno. Contudo se para cada classe terreno existe um TipoTerreno isso mostra que a sua classe Terrno tem problemas de modelagem.

Provavelmente vc está tentando usar uma classe por tipo para implementar algum metodo, mas ai que está o problema. Vc deveria usar o padrão strategy para isso. Sem conhecer o pq de haver um classe terreno fica mais complicado…

Por outro lado, na linha de eliminar o instanceof a todo o custo vc pode usar Terreno.isAssinableFrom() que é mais ou menos a mesma coisa… ou vc pode usar um Set interno de Animal com as classes de terrono que podem ser atravessdas por ele e depois simplesmente fazer terrenos.contain(terrenoPassado).

Uma outra opção é definir uma regra para que possa ou não atravesar conforme caracterisicas do terreno.
exemplo

class Animal {

boolean podeAtravessar(Terreno t ){
     return !t.isFlooed (); // pode atravessar desde que o terrebno não esteja inundado. (não seja "agua")
}

}

Criando várias propriedades para o terreno, o animal pode dicidir melhor e sem usar instanceof
Por exemplo uma Fenix poderia atravessar se isOnFire() for true. Ou uma pessoa poderia atravessar isIced apenas se tiver um objeto “patins de gelo” … não sei se ficou claro…

paulog

Entendo perfeitamente o seu ponto de vista. Mas enfim, quando eu quero dizer que cada terreno tenha a sua classe, e que isso é um requisito, é porque foi definido desta forma. Essa pequena modelagem faz parte de um jogo que devo desenvolver esse semestre em uma disciplina da universidade, e certos professores são bem restritivos em relação a certas partes da modelagem. Essa é uma das restrições:
“Cada terreno e animal deve possuir sua classe”

Sobre o enum, realmente, poderia acabar usando uma enum para cada tipo de terreno, então não mudaria nada.
Creio que não seria o padão strategy. Terreno é somente para determinar onde certo animal será usado (E também como será “desenhado” certas partes do jogo que correspondam a esse terreno).

Sobre ITerreno.isAssinableFrom() para mim é trocar seis por meia dúzia.

Animal conter uma coleção de terrenos em que ele pode acessar. Interessante, mas prático?

Agora como características do terreno foi muito bom (Ficou claro :slight_smile: ). Gostei muito dessa solução.
Verei se o projeto comporta ela. Realmente dá muita vantagem na construção de novos terrenos.

Obrigado sergiotaborda.

sergiotaborda

Então é um strategy. A definição de strategy é : Conforme o tipo use uma estratégia diferente para fazer a mesma coisa. No seu caso: conforme o tipo de terreno use uma estratégia de “desenho” diferente para “desenhar” o terreno. :wink:

Mas sim, é complicar demais.

Criado 18 de março de 2008
Ultima resposta 18 de mar. de 2008
Respostas 11
Participantes 4