When is it acceptable to use instanceof? [closed]

2019-02-01 22:25发布

I'm designing a game. In the game, various game objects extend different interfaces (and one abstract class) depending on what they need to be doing, and are passed to handlers which take care of items with a specific interface at defined intervals (they actually spread all their work out in a neat sort of way to make sure input/video/etc is always processed).

Anyway, some of these objects extend the abstract class Collider and are passed to a CollisionHandler. The Collider class and handler take care of everything technical involved in collision, and just ask that an object implement a collidesWith(Collider c) function, and modify itself based on what it has collided with.

Objects of many different classes will be colliding with one another, and will act very differently depending on what type of object they have collided with and its specific attributes.

The perfect solution seems to be to use instanceof like so:

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.
        }
    }
}

This actually seems like a legitimate place for instanceof. I just get this bad feeling. Like how if a goto made sense in some particular situation. Does the design feel fundamentally off to anyone? If so, what would you recommend doing to achieve the same behavior.

8条回答
forever°为你锁心
2楼-- · 2019-02-01 23:15

Traditional answer is, use a Visitor pattern. You add a new interface,

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

and a method,

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

Every object in your game implements a visit method, and every action you need implements a Visitor interface. So as soon as action visits an object, it is forced to do an action associated with that object.

You can of course be more detailed and not rely on the method dispatching mechanism.

Update: returning to the question header, it is always acceptable to use instanceof. It's your code, it's your language to use. Problem is, if there are many places in your code where you use instanceof, you'll inevitably miss one sooner or later, so that your code will silently fail without compiler there to help you. Visitor will make your life more painful during coding, as it will force you to implement the interface each time you change it, everywhere. But on a plus side, you won't miss a case this way.

Update 2: Please read the discussion below. Visitor will of course bind you and you'll feel constrained by it as soon as you have more that a dozen types. Moreover, if you need to dispatch events, e.g. collisions, basing on types of two or more objects, no visitor will help you (no instanceof, either): you will need to implement your own table of collision consequences which would map your type combinations to an object (I'd say Strategy, but am afraid discussion will grow tenfold) which would know how to deal with this particular collision.

An obligatory Stroustrup quote: "There is no substitute for: Intelligence; Experience; Taste; Hard work."

查看更多
Evening l夕情丶
3楼-- · 2019-02-01 23:17

In my opinion, what you have sketched above is a legitimate use of instanceof, and may be more readable than using a Visitor system, if each class only interacts with a few other classes as seen above.

The problem is that it has the potential to turn into pages of else-ifs for each of twenty types of enemies. But with instanceof, you can avoid that with some standard use of polymorphism (check for one Enemy class and treat all enemies alike, even if they're Orcs or Daleks or whatnot).

The Visitor pattern makes it much harder to do that. The most workable solution would be to have one top-level class that all your game objects derive from, and define collideWith() methods in that class for all its subclasses - but then have the default implementation of each just call the collideWith() for the supertype:

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.
}
查看更多
登录 后发表回答