什么时候可以使用instanceof?

Cas*_*sey 28 java polymorphism instanceof flawed-concept

我正在设计一款游戏.在游戏中,各种游戏对象根据他们需要做的事情扩展不同的接口(和一个抽象类),并传递给处理程序,处理程序按照定义的时间间隔处理具有特定接口的项目(它们实际上将所有工作分散开来以一种简洁的方式确保始终处理输入/视频/等).

无论如何,其中一些对象扩展了抽象类Collider并传递给CollisionHandler.Collider类和处理程序负责碰撞中涉及的所有技术,并且只要求对象实现collidesWith(Collider c)函数,并根据它碰撞的内容进行修改.

许多不同类的对象将彼此碰撞,并且将根据它们碰撞的对象的类型及其特定属性而以非常不同的方式起作用.

完美的解决方案似乎是像这样使用instanceof:

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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这实际上似乎是一个合法的地方.我只是感觉不舒服.就像一个goto在某些特定情况下有意义一样.设计是否对任何人都有根本感觉?如果是这样,你会建议做什么来实现相同的行为.

alf*_*alf 15

传统的答案是,使用访客模式.你添加一个新的界面,

interface Visitor {
     void visit(Enemy e);
     void visit(Food f);
     void visit(BeanOfLight bol);
}
Run Code Online (Sandbox Code Playgroud)

和方法,

public void visit(Visitor v) {
    visitor.visit(this);
}
Run Code Online (Sandbox Code Playgroud)

游戏中的每个对象都实现了一个visit方法,您需要的每个操作都会实现一个Visitor接口.因此,只要visits对象执行操作,就会强制执行与该对象关联的操作.

您当然可以更详细,而不是依赖于方法调度机制.

更新:返回问题标题,始终可以使用instanceof.这是你的代码,它是你的语言.问题是,如果您的代码中有许多地方使用instanceof,您迟早会不可避免地错过一个地方,这样您的代码就会在没有编译器的情况下无声地失败来帮助您.访问者会在编码过程中让您的生活更加痛苦,因为它会迫使您在每次更改界面时实施界面.但从好的方面来说,你不会错过这种情况.

更新2:请阅读以下讨论.访客当然会束缚你,只要你有十几种类型,你就会感到受到约束.此外,如果你需要根据两个或多个对象的类型调度事件,例如碰撞,没有访问者会帮助你(也没有instanceof):你需要实现自己的碰撞后果表,这会将你的类型组合映射到一个对象(我会说Strategy,但我担心讨论将增长十倍),这将知道如何处理这种特殊的碰撞.

强制性的Stroustrup引用:"没有替代品:智能;经验;品味;努力工作."

  • 添加到_visitable_类型的方法实际上应该被称为`accept(Visitor v)`; (3认同)

DJC*_*rth 7

通常建议访客类.使用Visitor,您实现了一个访问方法:

interface Visitor {
 void visit(Enemy e);
 void visit(Food f);
 void visit(BeanOfLight bol);
}
Run Code Online (Sandbox Code Playgroud)

但这实际上相当于:

class SomeNPC extends Collider {
  public void collidesWith( Enemy enemy )
  public void collidesWith( Food food )
  public void collidesWith( Bullet bullet )
}
Run Code Online (Sandbox Code Playgroud)

这两者都有缺点.

  1. 您必须实现所有这些,即使您的对象的响应在每种情况下都相同
  2. 如果添加要碰撞的新类型的对象,则必须编写一个方法来实现与每个对象的碰撞.
  3. 如果系统中的一个对象对27种碰撞器的反应不同,但其他一切反应方式相同,那么你仍然需要为每个类编写27个访问者方法.

有时最简单的方法是:

collidesWith(Object o) {
  if (o instanceof Balloon) {
    // bounce
  } else {
    //splat
  }
Run Code Online (Sandbox Code Playgroud)

它的优势在于它可以保持对象如何对与该对象相关的事物做出反应的知识.它还意味着如果Balloon具有子类RedBalloon,BlueBalloon等,我们不必考虑这一点,就像访问模式一样.

不使用instanceof的传统观点是它不是OO,你应该使用多态.然而,你可能有兴趣在这篇文章中:当多态性失败,由史蒂夫·耶格也解释了为什么有时候的instanceof是正确的答案.


Rot*_*sor 5

奇怪的是,还没有人发布“未中断”的访问者模式实现。所谓不破坏,是指不依靠访客的副作用。为此,我们需要访问者返回一些结果(我们称之为R):

interface ColliderVisitor<R> {
     R visit(Enemy e);
     R visit(Food f);
     R visit(BeanOfLight bol);
     R visit(SomeNpc npc);
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们进行修改accept以接受新访客:

interface Collider {
    <R> R accept(ColliderVisitor<R> visitor);
}
Run Code Online (Sandbox Code Playgroud)

Collider的具体实现将必须调用适当的visit方法,如下所示(我假设Food implements Collider,但这不是必需的):

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

现在要实现冲突,我们可以执行以下操作:

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()));
}
Run Code Online (Sandbox Code Playgroud)

您可以看到编译器可以帮助您找到忘记指定行为的所有位置。这不是某些人声称的责任,而是优点,因为您可以始终使用默认实现将其禁用:

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; }
}
Run Code Online (Sandbox Code Playgroud)

您还将需要某种方式来重用代码以将(Food,SomeNpc)和(SomeNpc,Food)冲突,但这不在此问题的范围内。

如果您认为这太冗长,那是因为。在具有模式匹配的语言中,这可以分几行完成(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
Run Code Online (Sandbox Code Playgroud)