Instanceof, typecast e afins

Porque utilizar instanceof e typecast é considerado má-prática?

Como funciona Integer.parseInt? Ele não faz um typecast?

Pergunto devido a esse trecho:

			if (nodeList.get(i) instanceof GroupNode){
				openGroupNodes.add(nodeList.get(i));
			}

A estrutura é mais ou menos essa:
Classe Node abstrata com InternalNode e GroupNode como filhos. Como posso eliminar esse instanceof.
Certa vez, me falaram que se tem necessidade de instanceof, tem algo errado na arquitetura.
Preciso gerar niveis infinitos (design time) de subnodes. Um grupo pode conter diversos niveis de subgrupos. Então, tenho que armazenar cada abertura para realizar o respectivo fechamento, no momento apropriado, baseado nos seus níveis.

Obrigado.

Porque Java é uma linguagem estática, ainda que meia-bomba, então você deve deixar o compilador tratar tipos. Quando você precisa explicitamente saber ou converter o tipo de um objeto é bem provável que você esteja fazendo algo errado.

Claro que para toda regra tem exceção. Coisas toscas como type erasure em generics, meta-programação fraca e falta de multi-métodos fazem com que você não consiga fugir completamente de verificar tipos manualmente em situações complexas (e não comuns para o programador do dia-a-dia).

typecast != conversão

Isto é um typecast:

Animal animal = new Cachorro();
Object obj = (Object) animal; // upcast; não precisa ser explicitado. É equivalente a:
Object obj = animal;
Cachorro cach = (Cachorro) animal; // downcast

Conversão é conversão (entre tipos não relacionados, como int e String, por exemplo).

O downcast não é muito recomendado, embora não seja proibido, devido à geração de dependências no seu programa. Mas você não precisa ficar se matando para tentar eliminar downcasts completamente no seu programa. Se você usar um framework de injeção de dependências (ou inversão de controle), então você já resolve esse problema de maneira mais limpa.

[quote=celso.martins]Porque utilizar instanceof e typecast é considerado má-prática?

Como funciona Integer.parseInt? Ele não faz um typecast?

Pergunto devido a esse trecho:

			if (nodeList.get(i) instanceof GroupNode){
				openGroupNodes.add(nodeList.get(i));
			}

A estrutura é mais ou menos essa:
Classe Node abstrata com InternalNode e GroupNode como filhos. Como posso eliminar esse instanceof.
[/quote]

Pela sua descrição parece que está usando o padrão composite.
Parece-me tb que vc quer adicionar os nodos apenas se são grupos , ou seja, se não são folhas.
Existem duas formas para resovler isto:

  1. colocar um booleano em Nodo
abstract class Nodo{

   public abstract boolean isGroup();
}

class InternalNode exends node{

   public final boolean isGroup(){ return false}
}

class GroupNode exends node{

   public final boolean isGroup(){ return true}
}
  1. colocar um inteiro em Nodo
abstract class Nodo{

   public abstract int childrenCount();
}

class InternalNode exends node{

   public final int childrenCount(){ return 0}
}

class GroupNode exends node{

   public final int childrenCount(){ return this.listaSubNodos.getSize()}
}

A primeira forma é mais especifica e permite fazer algo como

			if (nodeList.get(i).isGroup()){
				openGroupNodes.add(nodeList.get(i));
			}

Mas de certa forma viola o encapsulamento e o padrão composite. Não que não seja possivel
é um trade-off se realmente não existir outra opção. Isto é usado por exemplo no nodos do Swing do JTree

A segunda forma é menos obvia

			if (nodeList.get(i).childrenCount()>0){
				openGroupNodes.add(nodeList.get(i));
			}

E funciona apenas no contexto. Não estamos realmente destinguindo que o nodo é de grupo, mas se ele contém mais nodos.
(apenas se for de grupo contem nodos, mas memso sendo de grupo pode ter zero nodos)
O ponto é que se isto é parte de um algoritmo “de limpeza” não é necessário guardar os nodos de grupo que têm zero nodos filhos
e acaba funcionando melhor que o booleano. Como o nodo é suposto ter filhos não é violação do encapsulamento nem do padrão composite. É uma melhor opção em geral, embora não permite destinguir realmente os nodos grupo dos não-grupo. Esse é o trade-off.

Obrigado ao Thingol, Philip e Sérgio pelas respostas.

Sérgio, não seria exatamente um algorítmo de limpeza e sim de “montagem”. Obrigado por ter indicado o Pattern, vou dar uma olhada no Gamma et al.
Não creio que terei grupos sem nodos internos. Pode acontecer de um grupo conter apenas subgrupos. Mas eu teria que especifica-los de um caso ou de outro, inclusive numa montagem errada da IDL (Interface Defition Language).

Achei interessante as soluções, só não entendi a quebra de encapsulamento e do Pattern com a forma booleana. Neste último caso, até normal porque tenho pouco (ou nenhum) conhecimento sobre o Composite. Ontem estava lendo sobre o padrão Abastract Factory, no livro do Braude, e tive a sensação de que talvez resolvesse o meu problema. Mas como sempre tenho essa mania de ver nos meus projetos a aplicação do padrão que eu esteja lendo, pode ser que eu tenha me precipitado.

Segue abaixo o algoritmo completo do método. Ele é simples, se tiver cabra na lista de grupos e esse cabra for do nível atual ou maior, significa que ele precisa ser fechado. Existem “quedas” de varios niveis, por ex, do nivel 4 para o 1, assim, teria que fechar o 3, 2 e 1. Ainda não sei se funciona, vou escrever os testes hoje, fiz só o chinês. Isso é uma refatoração de um método HORRIVEL, mas que estava funcionando. Não ignorei a sua ajuda no outro tópico, apenas não implementei com Hash ainda, por isso está com List.

	public static String transform(List<Node> nodeList){
		if (nodeList.isEmpty()){
			// Logar lista de nodes vazia
			return null;
		}
		String retorno = "";
		
		List<Node> openGroupNodes = new ArrayList<Node>();
		
		for (int i=0; i < nodeList.size(); i++){
			
			int lastGroup = openGroupNodes.size() -1;
			// Se o nível do grupo >= nivel atual, ele precisa ser fechado
			while ((openGroupNodes.size() > 0) && 
					(openGroupNodes.get(lastGroup).getLevel() >= nodeList.get(i).getLevel())){
				GroupNode groupNode = (GroupNode) openGroupNodes.get(lastGroup);
				retorno += groupNode.getCloseTag();
				openGroupNodes.remove(openGroupNodes.get(lastGroup));
				lastGroup--;
			}

			retorno += nodeList.get(i).toXML();
			if (nodeList.get(i) instanceof GroupNode){
				openGroupNodes.add(nodeList.get(i));
			}
		}
		
		return retorno;
	}

Obrigado.
Abraços!