2010-10-14 20 views
16

AnimalCómo evitar grandes sentencias if y instanceof

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

ciervos

public class Deer extends Animal { 

public Deer(String name) { 
    super(name); 
} 

public void runAway() { 
    System.out.println("Running..."); 
} 

} 

TestAnimals

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<Animal> li = new ArrayList<Animal>(); 
    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 recorrer la lista sin tener que echar? En el caso anterior se parecen de bien, pero si usted tiene muchas extensiones de la clase base y luego vamos a necesitar que muchos, si bloquear también. ¿Existe un patrón de diseño o principio para abordar este problema?

+0

¿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

+0

¿Hay alguna forma mejor de hacer algo incorrecto? – InsertNickHere

+1

¿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

Respuesta

29

una manera elegante de evitar instanceof sin inventar un nuevo método artificial en la clase base (con un nombre no descriptivo como performAction o doWhatYouAreSupposedToDo) es utilizar el visitor pattern.Aquí está 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 ciervos

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

TestAnimals

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<Animal> li = new ArrayList<Animal>(); 
     li.add(lion); 
     li.add(deer1); 
     li.add(deer2); 
     for (Animal a : li) 
      a.accept(new ActionVisitor());   // <-- Accept/visit. 
    } 
} 
+0

Sí, tienden a estar de acuerdo. No puedo resistirme a votar por el visitante. Me encanta, pero creo que haces más que yo. +1 –

+0

Es con respecto a la descripción que agregó más adelante, y el visitante agregará cualquier nuevo 'Animal' requerirá la modificación de' AnimalVisitor'. Considerando que, la otra opción es buena para ir sin esta pequeña modificación. ¿Lo que usted dice? –

+0

@Adel Ansari, ese es un buen punto. Depende de la situación que diga. Depende de si desea que las implementaciones de acción específicas se recopilen en una clase o si desea que se distribuyan en las diferentes clases. Ambos son preferibles en diferentes situaciones. Sin embargo, en este escenario, 'roar' y' runAway' son tan diferentes que no me gustaría inventar un nombre común para los dos. – aioobe

5

Sí proporcionan un método llamado action() en la clase abstracta, incorporarlo a la vez de la clase hija, una rugirá otra se escaparía

2

Si el método no es polimórfica no se puede hacer sin el molde. Para hacerlo polimórfico, declare un método en la clase base y anule en las clases descendientes.

12

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

ciervos

public class Deer extends Animal { 

public Deer(String name) { 
    super(name); 
} 

public void exhibitNaturalBehaviour() { 
    System.out.println("Running..."); 
} 

} 

Testan animalesque

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

} 
} 
+1

Además, esto podría extenderse fácilmente para usar un patrón de estrategia. Suministro en la acción a realizarse (con un setter separado, o en constructor) en doWhatYouSupposeToDo(). Aunque podría ser mejor llamarlo act() o perform() – Steven

+0

@Steven: tiendo a estar de acuerdo, con sus dos sugerencias. Me acabo de ocurrir con este nombre arbitrariamente. Sí, suena estúpido y lindo; D. –

+3

Nombre de método alternativo propuesto: exhibitNaturalBehaviour() – mikera

2

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

Así que los dos mejores soluciones son:

  • Tener un método común para las dos clases concretas (por lo que se define como abstract en Animal)
  • independiente Lion de Deer desde el principio, y tienen dos listas diferentes .
1

Considere la posibilidad de agregar una interfaz para la acción (Ruido, 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.

2

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

ver este código 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 _  =>() 
    } 
    } 
} 
+0

No lo hago si funciona, pero se ve bien. – Emil

+0

¿por qué ambos son 'a'? – Emil

+2

@Emil: Funciona. Esta probado :) –

1

El enfoque más simple es tener la superclase implementar 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(); 
    } 
    } 
} 
+0

buen approach.i me gusta. – Emil

+0

El uso de Enum en este caso no es muy flexible, ya que solo existirá una única instancia de comportamiento de ciervos. (El comportamiento del ciervo no puede, por ejemplo, tener una referencia al objeto de ciervo relacionado, ya que el comportamiento se comparte con otras instancias de Ciervo). Además, no es muy elegante poder escribir myDeer.roar(). – aioobe

+0

@aioobe: sí su derecho. No pensé mucho sobre eso. – Emil

3

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.

+0

Creo que es justo mencionar esto. Sin embargo, en la gran mayoría de los casos, la facilidad de mantenimiento superará con creces una ganancia modesta de rendimiento. Con el patrón de visitante, agregar un nuevo tipo de animal expondrá inmediatamente cualquier lugar que necesite actualizarse, ya que los visitantes que ya existían no compilarán. If-else cadenas que utilizan instanceof compilarán felizmente y hay una buena posibilidad de que no descubras que has efectuado el módulo extraño hasta que se note el comportamiento incorrecto. – derekv

+0

@derekv ¿Dónde crees que instanceof es más apropiado? Digamos que java.lang.Number se ha escrito con un patrón de visitante. ¿Realmente valdría la pena escribir un visitante cada vez que quiera verificar el tipo de instancia de un número? ¿Especialmente si solo quisieras hacer algo si fuera un Doble, por ejemplo? Además, si está creando una biblioteca y desea que los usuarios puedan extender un tipo en su biblioteca para la que ha creado un patrón de visitante, los usuarios de su biblioteca no podrían agregar un método para su nueva biblioteca. escriba a la interfaz de visitante en su biblioteca, a menos que quieran tenedor. – Andy

+0

No creo que haya una solución que funcione mejor para cada situación, lo que yo defiendo es un mejor esfuerzo para saber qué herramientas tiene y tomar una decisión que mejor se adapte a las necesidades del problema que se está resolviendo, y respeto por aquellos quién mantendrá el código más tarde. Casi en todas partes he visto 'instanceof' en el código de la aplicación, es un olor y no un ciclo de rendimiento crítico o tal. También soy consciente de varios problemas con el patrón de visitante. En cuanto a la pregunta de su biblioteca, necesitaría ver algunos detalles, pero es un punto interesante, podría ser una buena pregunta por separado. – derekv

Cuestiones relacionadas