Resulta que instanceof es más rápido que el patrón de visitante presentado anteriormente; Creo que esto debería hacernos cuestionarnos, ¿el patrón de visitantes es realmente más elegante que el de cuándo está haciendo lo mismo más lentamente con más líneas de código?
Aquí está mi prueba. Comparé 3 métodos: el patrón de visitante anterior, instanceof y un campo de tipo explícito en Animal.
OS: Windows 7 Enterprise SP1, 64-bit
Procesador: CPU Intel (R) Core (TM) i7 860 @ 2,80 GHz 2,93 GHz
RAM: 8,00 GB
JRE: 1,7.0_21-b11, 32 bits
import java.util.ArrayList;
import java.util.List;
public class AnimalTest1 {
public static void main(String[] args) {
Animal lion = new Lion("Geo");
Animal deer1 = new Deer("D1");
Animal deer2 = new Deer("D2");
List<Animal> li = new ArrayList<Animal>();
li.add(lion);
li.add(deer1);
li.add(deer2);
int reps = 10000000;
long start, elapsed;
start = System.nanoTime();
for (int i = 0; i < reps; i++) {
for (Animal a : li)
a.accept(new ActionVisitor()); // <-- Accept/visit.
}
elapsed = System.nanoTime() - start;
System.out.println("Visitor took " + elapsed + " ns");
start = System.nanoTime();
for (int i = 0; i < reps; i++) {
for (Animal a : li) {
if (a instanceof Lion) {
((Lion) a).roar();
} else if (a instanceof Deer) {
((Deer) a).runAway();
}
}
}
elapsed = System.nanoTime() - start;
System.out.println("instanceof took " + elapsed + " ns");
start = System.nanoTime();
for (int i = 0; i < reps; i++) {
for (Animal a : li) {
switch (a.type) {
case Animal.LION_TYPE:
((Lion) a).roar();
break;
case Animal.DEER_TYPE:
((Deer) a).runAway();
break;
}
}
}
elapsed = System.nanoTime() - start;
System.out.println("type constant took " + elapsed + " ns");
}
}
abstract class Animal {
public static final int LION_TYPE = 0;
public static final int DEER_TYPE = 1;
String name;
public final int type;
public Animal(String name, int type) {
this.name = name;
this.type = type;
}
public abstract void accept(AnimalVisitor av); // <-- Open up for visitors.
}
class Lion extends Animal {
public Lion(String name) {
super(name, LION_TYPE);
}
public void roar() {
// System.out.println("Roar");
}
public void accept(AnimalVisitor av) {
av.visit(this); // <-- Accept and call visit.
}
}
class Deer extends Animal {
public Deer(String name) {
super(name, DEER_TYPE);
}
public void runAway() {
// System.out.println("Running...");
}
public void accept(AnimalVisitor av) {
av.visit(this); // <-- Accept and call visit.
}
}
interface AnimalVisitor {
void visit(Lion l);
void visit(Deer d);
}
class ActionVisitor implements AnimalVisitor {
public void visit(Deer d) {
d.runAway();
}
public void visit(Lion l) {
l.roar();
}
}
Resultados del ensayo:
visitantes tomaron 920842192 ns
instanceof tomó 511 837 398 ns
tipo constante llevó 535296640 ns
Este patrón visitante introduce 2 llamadas a métodos adicionales que son innecesarios con instanceof. Esta es probablemente la razón por la cual es más lento.
No es que el rendimiento sea la única consideración, pero observe cómo 2 instancias son más rápidas que incluso una declaración de cambio de 2 cajas. Muchas personas se han preocupado por el rendimiento de instanceof, pero esto debería poner la preocupación a descansar.
Como desarrollador de Java, me siento frustrado cuando las personas tienen una actitud dogmática acerca de evitar el uso de instanceof, porque varias veces en mi trabajo he querido limpiar o escribir nuevos códigos de limpieza utilizando instanceof, pero mis compañeros de trabajo/los superiores no aprobaron este enfoque, porque han aceptado más o menos ciegamente la idea de que nunca se debe usar instanceof. Me siento frustrado porque este punto es a menudo llevado a casa con ejemplos de juguetes que no reflejan las preocupaciones comerciales reales.
Siempre que persiga el diseño de software modular, siempre habrá ocasiones en que las decisiones dependientes del tipo deban aislarse de los tipos en cuestión, de modo que los tipos tengan la menor cantidad de dependencias posible.
Este patrón de visitante no rompe la modularidad, pero no es una alternativa superior a instanceof.
¿Los métodos deben tener diferentes nombres (rugido, runAway)? La idea de polimorfismo es tener los mismos métodos en las subclases, con la JVM llamando a la adecuada. – dariopy
¿Hay alguna forma mejor de hacer algo incorrecto? – InsertNickHere
¿Cuál es el * intento * del código en 'TestAnimals'? ¿Es 'hacer lo específico del animal para cada animal'? O '' rugir' todos 'Lion's,' runAway' todo 'Deer'' y nada más? – AakashM