当前位置: 首页>>编程语言>>正文


什么时候可以使用instanceof?

枭龙 编程语言 , , , , 去评论

问题描述

我正在设计一款游戏在游戏中,各种游戏对象根据他们需要做什么来扩展不同的界面(和一个抽象类),并被传递给处理程序,这些处理程序以定义的间隔处理具有特定界面的项目(他们实际上将所有的工作都展开以一种整洁的方式来确保总是处理输入/视频/等等)。

无论如何,这些对象中的一些扩展了抽象类Collider并被传递给CollisionHandler。对撞机类和处理程序处理涉及碰撞的一切技术,只要求一个对象实现collidesWith(对撞机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.
        }
    }
}

这实际上似乎是一个合法的例子。 I just get this bad feeling. Like how if a goto made sense in some particular situation.设计从根本上脱离了任何人吗?如果是这样,你会建议做什么来实现同样的行为。

最佳解决办法

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

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

和一种方法,

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

您的游戏中的每个对象都会实现visit方法,并且您需要的每个操作都会实现Visitor接口。所以一旦操作visits一个对象,就被迫做一个与该对象相关联的动作。

你当然可以更详细,不依赖方法调度机制。

更新:返回问题标题,始终可以使用instanceof。这是你的代码,这是你使用的语言。问题是,如果您的代码中有很多地方使用instanceof,那么您将不可避免地错过一个或者更晚的版本,这样您的代码将无声无效,无需编译器来帮助您。访客将使您的生活在编码过程中更加痛苦,因为它会迫使您在每次更改时实现界面。但是在这方面,你不会错过这种情况。

更新2:请阅读下面的讨论。访客当然会束缚你,只要你有十几种类型,你就会受到限制。此外,如果您需要调度事件,例如碰撞,基于两个或更多对象的类型,没有访客将帮助您(否instanceof):您将需要实现自己的冲突后果表,将映射您的类型组合到一个对象(我会说Strategy,但恐怕讨论会增长十倍),这将知道如何应对这种特殊的碰撞。

一个强制性的Stroustrup引用:“没有替代:智力;经验;品味;努力工作”。

次佳解决办法

经常推荐访客类。访问者可以实现访问方式:

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

但这实际上相当于:

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

这两个都有缺点。

  1. 你必须实现所有这些,即使对象的响应在每种情况下是相同的

  2. 如果您添加一种新的对象与之冲突,您必须编写一个方法来实现与每个对象的冲突。

  3. 如果您的系统中的一个对象与27种碰撞器有不同的反应,但其他一切都以相同的方式反应,则您仍然必须为每个类写入27个访问者方法。

有时最简单的方法是做:

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

它具有的优点是它保持对物体对物体的反应的知识。这也意味着如果气球具有RedBalloon,BlueBalloon等子类,那么我们不用考虑这一点,就像访问者模式一样。

不使用instanceof的传统参数是它不是OO,你应该使用多态。但是您可能对这篇文章感兴趣:When Polymorphism Fails, by Steve Yegge这解释了为什么instanceof有时是正确的答案。

第三种解决办法

奇怪的是没有人发布了”not broken”访客模式实现。而不是破坏我的意思是不依赖于访客的副作用。为此,我们需要访问者返回一些结果(我们称之为R):

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

接下来,我们修改accept接受新的访问者:

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

碰撞机的具体实现将不得不调用正确的visit方法,像这样(我假设Food implements Collider,但这不是必需的):

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

现在要实现碰撞,我们可以这样做:

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

您可以看到编译器可以帮助您找到忘记指定行为的所有位置。这不是一些人声称的责任,而是因为您总是可以通过使用默认实现来禁用它:

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

您还需要一些方法来重复使用(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

第四种办法

您可以使用这里的访问者模式,其中Collider的子类将为每种可能遇到的碰撞类型实现一种单独的方法。所以你的方法可能会变成:

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

你得到这个想法您的模型中的奇怪之处在于,一个Collider基类必须知道所有潜在的子类才能定义该类型的方法。其中的一部分与访问者模式的问题有关,但也是因为对撞机被组合成Visitor。我建议寻找访问者和对撞机的分离,以便您可以定义当碰撞发生时您希望行为的方式。对于碰撞机来说,这意味着它们可以根据内部状态改变它们的行为方式。说他们是无形的,而不是正常模式,隐藏或死亡。看客户端代码可能是:

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

第五种办法

在我看来,上面勾勒出的是使用instanceof的合法用法,如果每个类只与上述其他几个类进行交互,则可能比使用访问者系统更可读。

问题是它有可能变成else-if的页面为二十种敌人的每一个。但是使用instanceof,您可以通过一些标准的多态使用来避免这种情况(检查一个Enemy类,并对所有敌人进行处理,即使它们是OrcDalek也不管)。

访客模式使得它更难做到这一点。最可行的解决方案是拥有一个所有游戏对象派生的top-level类,并为其所有子类定义该类中的 collideWith()方法,但是每个类的默认实现只是为超类型调用 collideWith():

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

参考文献

注:本文内容整合自google/baidu/bing辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:gxnotes#qq.com(#替换为@)。

本文由《共享笔记》整理, 博文地址: https://gxnotes.com/article/164638.html,未经允许,请勿转载。
Go