Cómo evitar grandes sentencias if y instanceof

Animal

public abstract class Animal { String name; public Animal(String name) { this.name = name; } } 

León

 public class Lion extends Animal { public Lion(String name) { super(name); // TODO Auto-generated constructor stub } public void roar() { System.out.println("Roar"); } } 

Ciervo

 public class Deer extends Animal { public Deer(String name) { super(name); } public void runAway() { System.out.println("Running..."); } } 

TestAnimales

 public class TestAnimals { public static void main(String[] args) { Animal lion = new Lion("Geo"); Animal deer1 = new Deer("D1"); Animal deer2 = new Deer("D2"); List li = new ArrayList(); li.add(lion); li.add(deer1); li.add(deer2); for (Animal a : li) { if (a instanceof Lion) { Lion l = (Lion) a; l.roar(); } if (a instanceof Deer) { Deer l = (Deer) a; l.runAway(); } } } } 

¿Hay una mejor manera de iterar a través de la lista sin tener que realizar el colado? En el caso anterior, parece correcto, pero si tiene muchas extensiones de la clase base, entonces necesitaremos muchos bloques si también. Existe un patrón o principio de diseño para abordar este problema ?

Una forma elegante de evitar instanceof sin inventar algún nuevo método artificial en la clase base (con un nombre no descriptivo como performAction o doWhatYouAreSupposedToDo ) es usar el patrón de visitante . Aquí hay un ejemplo:

Animal

 import java.util.*; abstract class Animal { String name; public Animal(String name) { this.name = name; } public abstract void accept(AnimalVisitor av); // <-- Open up for visitors. } 

León y ciervo

 class Lion extends Animal { public Lion(String name) { super(name); } 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); } public void runAway() { System.out.println("Running..."); } public void accept(AnimalVisitor av) { av.visit(this); // <-- Accept and call visit. } } 

Visitante

 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(); } } 

TestAnimales

 public class TestAnimals { public static void main(String[] args) { Animal lion = new Lion("Geo"); Animal deer1 = new Deer("D1"); Animal deer2 = new Deer("D2"); List li = new ArrayList(); li.add(lion); li.add(deer1); li.add(deer2); for (Animal a : li) a.accept(new ActionVisitor()); // <-- Accept / visit. } } 

Animal

 public abstract class Animal { String name; public Animal(String name) { this.name = name; } public abstract void exhibitNaturalBehaviour(); } 

León

 public class Lion extends Animal { public Lion(String name) { super(name); } public void exhibitNaturalBehaviour() { System.out.println("Roar"); } } 

Ciervo

 public class Deer extends Animal { public Deer(String name) { super(name); } public void exhibitNaturalBehaviour() { System.out.println("Running..."); } } 

TestAnimales

 public class TestAnimals { public static void main(String[] args) { Animal[] animalArr = {new Lion("Geo"), new Deer("D1"), new Deer("D2")}; for (Animal a : animalArr) { a.exhibitNaturalBehaviour(); } } } 

Sí, proporcione un método llamado action() en la clase abstracta, impleméntelo en las dos clases de los niños, uno rugirá y otros escaparán

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.

Sistema operativo: Windows 7 Enterprise SP1, 64 bits
Procesador: Intel (R) Core (TM) i7 CPU 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 li = new ArrayList(); 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 de la prueba:

Visitante tomó 920842192 ns
instanceof tomó 511837398 ns
tipo constante tomó 535296640 ns

Este patrón de visitante introduce 2 llamadas a métodos extra que no son necesarias 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 statement 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 Java, me siento frustrado cuando las personas tienen una actitud dogmática sobre evitar el uso de instanceof, porque varias veces en mi trabajo he querido limpiar o escribir un nuevo código limpio usando instanceof, pero mis compañeros de trabajo / superiores no lo hicieron. apruebo 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á momentos en los 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.

Si su método no es polimórfico, no puede prescindir del elenco. Para hacerlo polimórfico, declare un método en la clase base y anule en las clases descendientes.

Aquí tienes una List de animales. Por lo general, cuando tienes una lista de Objetos, todos estos objetos deben poder hacer lo mismo sin ser convertidos.

Entonces, las mejores dos soluciones son:

  • Tener un método común para las dos clases concretas (así definido como abstract en Animal )
  • Separa al Lion de los Deer desde el principio, y tiene dos listas diferentes.

El soporte de coincidencia de patrones en el lenguaje elimina la necesidad del patrón de visitante feo.

Vea este código de Scala por ejemplo:

 abstract class Animal(name: String) class Lion(name: String) extends Animal(name) { def roar() { println("Roar!") } } class Deer(name: String) extends Animal(name) { def runAway() { println("Running!") } } object TestAnimals { def main(args: Array[String]) { val animals = List(new Lion("Geo"), new Deer("D1"), new Deer("D2")) for(animal <- animals) animal match { case l: Lion => l.roar() case d: Deer => d.runAway() case _ => () } } } 

Considere agregar una interfaz para la acción (rugido, huida, etc.) que se establece en el animal en el constructor. Luego, tenga un método abstracto como act () en la clase Animal que se llame similar a lo que tiene Adeel.

Esto le permitirá intercambiar acciones para actuar a través de un campo en cualquier momento.

El enfoque más simple es hacer que la súper clase implemente un comportamiento predeterminado.

 public enum AnimalBehaviour { Deer { public void runAway() { System.out.println("Running..."); } }, Lion { public void roar() { System.out.println("Roar"); } } public void runAway() { } public void roar() { } } public class Animal { private final String name; private final AnimalBehaviour behaviour; public Animal(String name, AnimalBehaviour behaviour) { this.name = name; this.behaviour = behaviour; } public void runAway() { behaviour.runAway(); } public void roar() { behaviour.roar(); } } public class TestAnimals { public static void main(String... args) { Animal[] animals = { new Animal("Geo", AnimalBehaviour.Lion), new Animal("Bambi", AnimalBehaviour.Deer), new Animal("D2", AnimalBehaviour.Deer) }; for (Animal a : animals) { a.roar(); a.runAway(); } } }