如何避免使用instanceof?

3

我定义了这个简单的方法:

public static boolean isBorder(int x, int y) throws CollisionDetectionException {
        try {
            if ( (levelItems[x][y] instanceof StaticGameObject && levelItems[x][y].isVisible()) ||
                (levelItems[x-1][y] instanceof StaticGameObject && levelItems[x-1][y].isVisible()) ||
                (levelItems[x][y+1] instanceof StaticGameObject && levelItems[x][y+1].isVisible()) ||
                (levelItems[x][y-1] instanceof StaticGameObject && levelItems[x][y-1].isVisible()) ||
                (levelItems[x-1][y-1] instanceof StaticGameObject && levelItems[x-1][y-1].isVisible()) ||
                (levelItems[x-1][y+1] instanceof StaticGameObject &&levelItems[x-1][y+1].isVisible()) ||
                (levelItems[x+1][y] instanceof StaticGameObject && levelItems[x+1][y].isVisible()) ||
                (levelItems[x+1][y+1] instanceof StaticGameObject && levelItems[x+1][y+1].isVisible()) ||
                (levelItems[x+1][y-1] instanceof StaticGameObject && levelItems[x+1][y-1].isVisible()) ) {
                return true;
            } else {
                return false;
            } 
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new CollisionDetectionException("Collision couldn't be checked because checking position " + x + "/" + y + " caluclated values below (0/0)");
        }
    }

如您所见,我有一个二维数组。现在我想要检查特定的位置((x/y) ->方法参数),如果二维数组的相邻区域中存在任何可见的StaticGameObject。

levelItems数组由所谓的GameObject组成。StaticGameObject是GameObject的直接子类。

有什么提示可以改进这个方法吗?

7个回答

13
在 GameObject 中添加一个方法。
bool isBorderObject() { return false; }

然后在StaticGameObject中进行覆盖

bool isBorderObject() { return true; }

将测试更改为

(levelItems[x][y].isBorderObject() && levelItems[x][y].isVisible())

此外,if语句也可以嵌套在for循环内

for (int i = x-1; i <= x+1; ++i) {
   for (int j = y-1; j <= y+1; ++j) {
       GameObject item = levelItems[i][j];
       if (item.isBorderObject() && item.isVisible()) 
           return true;
   }
}
return false;

谢谢,这看起来很不错!所以只剩下一个问题了,那就是那个冗长的if语句;) - RoflcoptrException
我修改了你的代码示例,使if语句和返回更加简洁,希望你不介意。 - matt b
你可以这样写:GameObject item = levelItems[x-1][y-1]; return item.isBorderObject() && item.isVisible();这样会更快。 - IAdapter
@matt b -- 当然不是 -- 我正在考虑这样做。但是代码是错误的 -- 他只想在条件为真时返回。我已经修复了它。 - Lou Franco

3

声明:我曾经在许多移动设备上开发过基于Java的游戏。

已经有人展示了如何简化所有这些“if”语句。我想要补充的是定义自己的CollisionDetectionException可能完全是杀鸡焉用牛刀。

首先,这种低级的Java细节在面向对象的层面上不存在:异常,特别是已检查异常,是Java的一个习惯性问题,真的没有必要。一些非常好的和非常令人印象深刻的Java框架大多都不需要它们,比如Spring。然后,很多由数百万行代码组成的非常令人印象深刻和强大的应用程序完全可以正常运行,而不需要使用已检查异常的概念,因为它们是使用没有这种概念的语言编写的(再次强调:在面向对象的层面上,“已检查异常”不存在,因此可以进行面向对象分析/面向对象设计到面向对象编程,而无需使用已检查异常)。

无论如何:没有理由将ArrayIndexOutOfBoundException转换为您自己的特定已检查异常。这意味着您计划将异常用于流程控制,这是一个巨大的错误。

然后,关于您的“isBorder”测试...您可能不需要它。您认为自己需要,但实际上并不需要。为什么要知道它是否是“边界”?我猜是为了检测碰撞。

您正在使用静态方法来检测是否为边界,而静态是面向对象的反义词。重要的是您在对象之间传递的消息。您可以让所有对象响应某些“isCollidingWith(...)”消息:是“边界”的对象知道它们是边界,它们将知道如何处理“isCollidingWith(...)”消息。

然后,您甚至可以更进一步:而不是进行“isCollidingWith(...)”,您可以拥有一些“resolveCollisionWith(...)”方法。

public class Wall implements MySuperAbstraction {

    int wallHitPoints = 42;

    boolean isWallStillUp = true;

    void resolveCollisionWith( SomeObject o ) {
        if ( o.isStrongEnoughToHarmWall ) {
           wallHitPoints--;
           isWallStillUp = wallHitPoints > 0;
        }
    }

}

这只是一小段代码,并且它没有考虑到SomeObject撞墙时需要反弹等问题,但核心观点是:在面向对象编程中,对象知道如何相互通信。它们知道如何处理各种传递的消息。
如果你想进行面向对象编程,我只能告诉你Java的staticinstanceof和checked exceptions绝不是正确的方法。基本上,它们是面向对象的反义词 :)

2

这样做可以消除您所拥有的极大的if块:

for(int col = x-1; col <= x+1; col++)
{
    for(int row = y-1; row <= y+1; row++)
    {
        if(levelItems[row][col] instanceof StaticGameObject && levelItems[row][col].isVisible())
            return true;
    }
}

这个解决方案仅仅减少了疯狂的if,而没有摆脱instanceof,正如你所看到的。

当然,在我的例子和你的例子中,你应该确保检查数组边界问题。


isVisible 在 GameObject 中被实现。 - RoflcoptrException
@User Unknown:啊,抱歉,我重新阅读了你的问题,现在我更好地理解了。无论如何,这个解决方案都可以减少极大的“if”。 :) - Brian S
嗯,我测试了一下,但似乎不能正常工作。边框不像以前那样被我的方法识别了。 - RoflcoptrException

2
一种对@Lou的解决方案进行修订的方法是只有一个方法和一个循环。
for (int i = 0; i < 9; i++) 
   if (levelItems[x-1 + i/3][y-1 + i%3].isBorderObjectVisible())  
       return true; 
return false; 

1

思考“控制反转”。

以GameObject类为例,如何引入这种方法:

public boolean
isBorder()
{
    return false;
}

还有在StaticGameObject类中的这个覆盖:

public boolean
isBorder()
{
    return self.isVisible();
}

简化上面的代码?


不能在这里使用静态关键字,并且仍然实现多态性。 - Lou Franco

1
另一个不错的技巧是拥有一个返回相邻单元格集合的函数。这样可以避免(或者至少减少)双重循环。这是更好的关注点分离;当你发现算法忘记了越界条件时,只需要在一个地方进行修复。
  public Set<GameObject> adjacentItems(int x, int y) {
    Set<GameObject> set = new HashSet<GameObject>();
    for (int i = -1; i < 2; ++i)
      for (int j = -1; j < 2; ++j)
        set.add(levelItems[x+i][y+j]);
    return set;
  }

  public boolean isBorder(int x, int y) {
    for (GameObject item : adjacentItems(x, y))
      if (item.isBorder() && item.isVisible())
        return true;
    return false;
  }

0

不要让这变得更加复杂。只需确保GameObject的所有子类都实现了isVisible方法即可。就是这样。

如果您需要同时区分可移动和不可移动,则需要两种方法:

  • isVisible -- 可见或不可见

  • isMovable -- 可移动或不可移动

您永远不需要使用instanceof

[从问题中无法确定,但似乎StaticGameObject类实际上是GameObject的一个子类,其isMovable()为false。]


是的,但GameObject还有其他直接子类可以在二维数组中。如果有另一个子类不是StaticGameObject,我想返回false。 - RoflcoptrException
然后将默认值设置为“False”。没有随机的“其他子类”会神奇地颠覆这一点。只需在超类中将其设置为默认值即可。 - S.Lott
@S.Lott:User Unknown特别想找到既是“StaticGameObject”又可见的实例。该数组可能包含可见但不正确类型的对象。 - Brian S
在这种情况下,StaticGameObject 必须正确实现 isVisible。你永远不需要使用 isinstance。你只需要让子类正确实现方法即可。 - S.Lott
@S.Lott:那么,你会如何解决这个问题:例如,MoveableGameObject既是GameObject的子类,有时还可以是可见的。我该如何判断它是MoveableGameObject还是StaticGameObject? - RoflcoptrException
@UserUnknown:如果可移动与不可移动是您的对象的一个特性,那么请添加一个 isMoveable 方法。 - S.Lott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接