utiliza una saber que para operador objeto instanciado instancia getclass esta ejemplo como clase java design polymorphism instanceof flawed-concept

java - una - ¿Cuándo es aceptable usar instanceof?



para que se utiliza el operador instanceof (8)

Estoy diseñando un juego. En el juego, varios objetos del juego extienden diferentes interfaces (y una clase abstracta) dependiendo de lo que deben hacer, y se pasan a los manejadores que se encargan de los elementos con una interfaz específica a intervalos definidos (en realidad difunden todo su trabajo) de una manera ordenada para asegurarse de que la entrada / video / etc siempre se procesa).

De todos modos, algunos de estos objetos extienden la clase abstracta Collider y se pasan a un CollisionHandler. La clase y el manejador de Collider se encargan de todo lo técnico involucrado en la colisión, y solo solicitan que un objeto implemente una función collidesWith (Collider c), y se modifiquen en función de lo que ha colisionado.

Objetos de muchas clases diferentes colisionarán unos con otros, y actuarán de forma muy diferente según el tipo de objeto con el que choquen y sus atributos específicos.

La solución perfecta parece ser el uso de instanceof así:

class SomeNPC extends Collider{ collidesWith(Collider c){ if(c instanceof enemy){ Fight it or run away depending on your attributes and theirs. } else if(c instanceof food){ Eat it, but only if it''s yellow. } else if(c instanceof BeamOfLight){ Try to move towards its source. } } }

Esto realmente parece ser un lugar legítimo para instanceof. Acabo de tener este mal presentimiento. Al igual que si un goto tiene sentido en una situación particular. ¿El diseño se siente fundamentalmente desacertado para cualquiera? Si es así, ¿qué recomendarías hacer para lograr el mismo comportamiento?


En mi opinión, lo que has esbozado arriba es un uso legítimo de instanceof, y puede ser más legible que usar un sistema Visitor, si cada clase solo interactúa con algunas otras clases como se ve arriba.

El problema es que tiene el potencial de convertirse en páginas de else-if para cada uno de los veinte tipos de enemigos. Pero con instanceof, puedes evitar eso con un uso estándar de polimorfismo (busca una clase Enemy y trata a todos los enemigos por igual, incluso si son Orc o Dalek o no).

El patrón Visitor hace que sea mucho más difícil hacer eso. La solución más viable sería tener una clase de nivel superior de la que derivan todos los objetos del juego, y definir los métodos collideWith () en esa clase para todas sus subclases, pero luego tener la implementación predeterminada de cada llamada justa collideWith () para el supertipo:

class GameObject { void collideWith(Orc orc) { collideWith((Enemy)orc); } void collideWith(Enemy enemy) { collideWith((GameObject)enemy); } ... void collideWith(GameObject object) { } } class SomeNPC extends GameObject { void collideWith(Orc orc) { // Handle special case of colliding with an orc } // No need to implement all the other handlers, // since the default behavior works fine. }


Es extraño que nadie haya publicado una implementación de patrón de visitante "no rota" todavía. Y por no estar roto me refiero a no confiar en los efectos secundarios del visitante. Para hacer eso, necesitamos que nuestros visitantes devuelvan algún resultado (llamémoslo R ):

interface ColliderVisitor<R> { R visit(Enemy e); R visit(Food f); R visit(BeanOfLight bol); R visit(SomeNpc npc); }

A continuación, modificamos accept para aceptar los nuevos visitantes:

interface Collider { <R> R accept(ColliderVisitor<R> visitor); }

las implementaciones concretas de collider tendrán que llamar al método de visit adecuado, así (supongo que Food implements Collider , pero esto no es necesario):

class Food implements Collider { @Override <R> R accept(ColliderVisitor<R> visitor) { return visitor.visit(this); } }

Ahora para implementar las colisiones podemos hacer algo como esto:

class SomeNpcCollisionVisitor implements ColliderVisitor<Action> { SomeNpcCollisionVisitor(SomeNpc me) { this.me = me; } SomeNpc me; @Override Action visit(Enemy they) { return fightItOrRunAway(me.attributes(), they.attributes()); } @Override Action visit(Food f) { return f.colour()==YELLOW ? eat(f) : doNothing; } @Override Action visit(BeamOfLight l) { return moveTowards(l.source()); } @Override Action visit(SomeNpc otherNpc) { // What to do here? You did not say! The compiler will catch this thankfully. } } class CollisionVisitor implements ColliderVisitor<ColliderVisitor<Action>> { // currying anyone? @Override Action visit(Enemy they) { return new EnemyCollisionVisitor(they); // what to do here? } @Override Action visit(Food f) { return new FoodCollisionVisitor(f); // what to do here? } @Override Action visit(BeamOfLight l) { return new BeamOfLightCollisionVisitor(l); // what to do here? } @Override Action visit(SomeNpc otherNpc) { return new SomeNpcCollisionVisitor(otherNpc); } } Action collide(Collider a, Collider b) { return b.accept(a.accept(new CollisionVisitor())); }

Puede ver que el compilador puede ayudarlo a encontrar todos los lugares donde olvidó especificar el comportamiento. Esto no es una responsabilidad, como afirman algunas personas, sino una ventaja porque siempre se puede desactivar mediante el uso de una implementación predeterminada:

class ColliderVisitorWithDefault<R> implements ColliderVisitor { final R def; ColliderVisitorWithDefault(R def) { this.def = def; } R visit(Enemy e) { return def; } R visit(Food f) { return def; } R visit(BeanOfLight bol) { return def; } R visit(SomeNpc npc) { return def; } }

También querrá una forma de reutilizar el código para colisiones de (Food, SomeNpc) y (SomeNpc, Food), pero esto está fuera del alcance de esta pregunta.

Si crees que esto es demasiado detallado, es porque lo es. En los idiomas que cuentan con la coincidencia de patrones, esto se puede hacer en varias líneas (ejemplo de Haskell):

data Collider = Enemy <fields of enemy> | Food <fields of food> | BeanOfLight <fields> | SomeNpc <fields> collide (SomeNpc npc) (Food f) = if colour f == YELLOW then eat npc f else doNothing collide (SomeNpc npc) (Enemy e) = fightOrRunAway npc (npcAttributes npc) (enemyAttributes e) collide (SomeNpc npc) (BeamOfLight bol) = moveTowards (bolSource bol) collide _ _ = undefined -- here you can put some default behaviour


Este es un uso de instancia que hará que la mayoría de la gente se encoja. Aquí hay un enlace útil que puede proporcionarle alguna información: http://www.javapractices.com/topic/TopicAction.do?Id=31

En cambio, un Collider debería tener algún tipo de método collide () que cada subclase anulará.


Esto podría ser algo para considerar. Usar instanceof puede ser útil, pero otra forma de considerar la idea del visitante es crear una clase Factory que examine un campo mutuo para determinar de qué tipo es el visitante.

Aún debe crear nuevas clases para cada implementación, pero se enrutan en una clase abstracta que define un método

public abstract class Qualifier{ public abstract String type(); //... } /** * QualifierFactory method. */ public static Qualifier getQualifier(Item sp, Config cf) { String type = cf.type(); if (XQualifier.type().equals(type)) return new XQualifier(sp, cf); else if (NQualifier.type().equals(type)) return new NQualifier(sp, cf); else if (Tools.isNone(type) || NoneQualifier.type().equals(type)) return new NoneQualifier(sp); else if (CSQualifier.type().equals(type)) return new CSQualifier(sp, cf);//... }

El objeto devuelto podría ser la acción.


Hay algunas maneras de manejar esto:

  • enumeraciones para los tipos de colisionador, ligeramente feo pero a prueba de fallas
  • Despacho de clases con controladores: es decir, algo así como Map<Class, CollisionHandler> , processCollision(source) CollisionHandler de la clase processCollision(source) pasada (o tipo enum) y llamas a processCollision(source) . Cada clase Colllider tiene su propio mapa de controladores.
  • En función de lo anterior, también puede crear una combinación de tipos de colisionadores y controladores de escritura, que es algo como Map<Pair<ColliderType>, CollisionHandler> para cada tipo de colisión nueva que necesite para crear un nuevo controlador. El lado positivo es que tales declaraciones pueden ser externas (inyección de dependencia), por lo que se pueden agregar nuevos NPC / Objetos junto con los controladores de Colisión.

De todos modos, primero asegúrate de que funcione de la manera que te sientas más cómodo y luego podrás refactorizarlo.


La clase de visitantes a menudo es recomendada. Con Visitante implementas un método de visita:

interface Visitor { void visit(Enemy e); void visit(Food f); void visit(BeanOfLight bol); }

Pero esto es de hecho equivalente a:

class SomeNPC extends Collider { public void collidesWith( Enemy enemy ) public void collidesWith( Food food ) public void collidesWith( Bullet bullet ) }

Ambos tienen desventajas.

  1. Debe implementarlos todos, incluso si la respuesta de su objeto es la misma en cada caso
  2. Si agrega un nuevo tipo de objeto para colisionar, debe escribir un método para implementar la colisión con él para cada objeto .
  3. Si un objeto en su sistema reacciona de manera diferente a 27 tipos de colisionadores, pero todo lo demás reacciona de la misma manera, aún debe escribir 27 métodos de visitante para cada clase .

A veces la forma más fácil es hacer:

collidesWith(Object o) { if (o instanceof Balloon) { // bounce } else { //splat }

Tiene la ventaja de que mantiene el conocimiento de cómo un objeto reacciona a las cosas que golpea con ese objeto. también significa que si Globo tiene subclases RedBalloon, BlueBalloon etc. no tenemos que tener esto en cuenta, como lo haríamos con el patrón de visitante.

El argumento tradicional para no usar instanceof es que no es OO, y deberías estar usando polimorfismo. Sin embargo, es posible que le interese este artículo: Cuando el polimorfismo falla, por Steve Yegge, que explica por qué la instancia es a veces la respuesta correcta .


La respuesta tradicional es usar un patrón de Visitante . Agrega una nueva interfaz,

interface Visitor { void visit(Enemy e); void visit(Food f); void visit(BeanOfLight bol); }

y un método,

public void visit(Visitor v) { visitor.visit(this); }

Cada objeto en su juego implementa un método de visit , y cada acción que necesita implementa una interfaz de Visitor . Entonces, tan pronto como la acción visits un objeto, se ve forzado a realizar una acción asociada con ese objeto.

Por supuesto, puede ser más detallado y no depender del mecanismo de envío de métodos.

Actualización: volviendo al encabezado de la pregunta, siempre es aceptable usar instanceof . Es tu código, es tu lenguaje para usar. El problema es que si hay muchos lugares en tu código donde usas instanceof , inevitablemente perderás uno antes o después, de modo que tu código fallará silenciosamente sin el compilador para ayudarte. El visitante hará que su vida sea más dolorosa durante la codificación, ya que le obligará a implementar la interfaz cada vez que la cambie, en cualquier lugar. Pero en un lado positivo, no te perderás un caso de esta manera.

Actualización 2: Lea la discusión a continuación. El visitante, por supuesto, te vinculará y te sentirás limitado tan pronto como tengas más de una docena de tipos. Además, si necesita enviar eventos, por ejemplo, colisiones, basándose en tipos de dos o más objetos, ningún visitante lo ayudará (tampoco lo hará): tendrá que implementar su propia tabla de consecuencias de colisión que correlacionará sus combinaciones de tipos a un objeto (yo diría Strategy , pero me temo que la discusión se multiplicará por diez) que sabría cómo lidiar con esta colisión en particular.

Una cita obligatoria de Stroustrup: "No hay sustituto para: inteligencia, experiencia, gusto, trabajo duro".


Puede usar un patrón de visitante aquí donde las subclases de Collider implementarían un método diferente para cada tipo de colisión que pueda encontrar. Entonces tu método podría convertirse en:

class SomeNPC extends Collider { public void collidesWith( Enemy enemy ) {} public void collidesWith( Food food ) {} public void collidesWith( Bullet bullet ) {} public void doCollision( Collider c ) { if( c.overlaps( this ) ) { c.collidesWith( this ); } } }

Entiendes la idea. Lo extraño de su modelo es que una clase base de un Colisionador debe conocer todas las posibles subclases para definir un método para ese tipo. Parte de esto tiene que ver con el problema del patrón de visitante, pero también es porque Collider se combina en Visitor. Sugiero que busque una separación entre el visitante y el colisionador para que pueda definir cómo desea comportarse cuando ocurren las colisiones. Lo que eso significa para sus colisionadores es que pueden cambiar la forma en que se comportan a una colisión en función del estado interno. Dicen que son invulnerables frente al modo normal, ocultos o muertos. Mirando el código del cliente puede ser:

collider1.getCollisionVisitor().doCollision( collider2 ); collider2.getCollisionVisitor().doCollision( collider1 );