摆脱`instanceof`

时间:2012-01-03 11:32:29

标签: java types sprite instanceof

在我写的基于精灵的游戏中,2D网格中的每个字段都包含一堆精灵。最重要的是最重要的。

在游戏的规则模块中,我有很多这样的代码:

public boolean isGameWon(Board board) {
    for (Point point : board.getTargetPoints())
        if(!(board.getTopSpriteAt(point) instanceof Box))
            return false;
    return true;
}

Upadate: //Do something会计算每个Box之上是否有Target。我不知道如何简单地将doSomething()添加到Sprite,除非doSomething()如果精灵是一个盒子,则返回1,否则返回0。 (这与instanceof相同)。


我知道instanceof被认为是有害的,因为它杀死了面向对象编程的想法。

但我不知道如何在我的情况下修复代码。以下是我的一些想法:

  • 我认为简单地向isABox()界面添加Sprite方法不会更好。
  • 如果Box是一个接口会有帮助吗,那么其他类可以获得相同的权限吗?
  • 我应该尝试做一些像花样匹配/双重调度这样的东西,像访客一样的模式吗?
  • 规则模块是否可以与类型密切配合,只是因为它应该知道它们的语义?
  • 规则模块策略模式的整个想法是否有缺陷?
  • 将规则构建到精灵中是没有意义的,因为在添加新类型时它们都必须被更改。

我希望你尝试过类似的东西,并能指出我正确的方向。

11 个答案:

答案 0 :(得分:7)

使用polymorphism

class Sprite {
    ..
    someMethod(){
    //do sprite
    }
    ..
}

class Box extends Sprite {
    ..
    @Overrides
    someMethod(){
    //do box
    }
    ..
}

所以,你只需要在你的例子中调用sprite.someMethod()。

答案 1 :(得分:5)

Instanceof :(差不多)总是有害

我看了你帖子的所有答案,并试图了解你在做什么。我得出的结论是instanceof 完全你想要什么,你的原始代码样本很好。

你澄清说:

  • 违反了Liskov替换原则,因为没有任何Box代码使Sprite代码无效。

  • 使用对instanceof的回复来分叉代码。这就是为什么人们说实例是坏的;因为人们这样做:

    if(shape instanceof Circle) {
        area = Circle(shape).circleArea();
    } else if(shape instanceof Square) {
        area = Square(shape).squareArea();
    } else if(shape instanceof Triangle) {
        area = Triangle(shape).triangleArea();
    }
    

    这就是人们避免使用instanceof的原因。但这不是你在做什么。

  • Box与赢得游戏之间存在一对一关系(没有其他精灵可以赢得比赛)。因此,您不需要额外的“赢家”精灵抽象(因为Boxes == Winners)。

  • 您只需检查该主板,以确保每个顶级商品 一个Box。这正是instanceof的目的。

其他人的回答(包括我自己的回答)增加了一个额外的机制来检查Sprite是否是一个Box。但是,它们不会增加任何稳健性。实际上,您正在使用已经由语言提供的功能,并在您自己的代码中重新实现它们。

Tomas Narros认为你应该在代码中区分“语义类型”和“java类型”。我不同意。您已经确定您拥有一个java类型Box,其子类为Sprite。所以你已经掌握了所需的所有信息。

在我看来,拥有第二个独立机制也报告“我是一个盒子”,违反了DRY(不要重复自己)。这意味着对于同一条信息没有两个独立的来源。您现在必须维护枚举和类结构。

所谓的“利益”是能够围绕完全填满目的的关键词旋转,并且在以更有害的方式使用时是有害的。


黄金法则使用你的头。不要把规则当成事实。问他们,了解他们为什么在那里,并在适当的时候弯曲它们。

答案 2 :(得分:3)

基本重载是去这里的方法。它是Sprite类层次结构,它应该知道该做什么以及如何做,如:

interface Sprite {
    boolean isCountable();
}


class MyOtherSprite implements Sprite {
    boolean isCountable() {
        return false;
    }
 }

 class Box implements Sprite {
    boolean isCountable() {
        return true;
    }
}

int count = 0;
for (Point point : board.getTargetPoints()) {
    Sprite sprite = board.getTopSpriteAt(point);
    count += sprite.isCountable() ? 1 : 0;
}

修改 您对问题的编辑不会从根本上改变问题。你所拥有的是一些仅适用于Box的逻辑。再次,将该特定逻辑封装在Box实例中(参见上文)。你可以进一步为你的精灵创建一个通用的超类来定义isCountable()的默认值(请注意,该方法类似于isBox一,但从设计角度来看实际上更好,因为它没有意义圆圈有一个isBox方法 - Box应该还包含一个isCircle方法吗?)。

答案 3 :(得分:3)

这是我的尝试。考虑使用不同的Sprite类型定义枚举:

class Sprite {
    public enum SpriteType {
         BOX, CAT, BOTTLE, HUMAN, TARGET, /* ... */, SIMPLE;
    }

    public SpriteType getSpriteType(){
       return SIMPLE;
    }
}


class Box extends Sprite {
    @Override
    public SpriteType getSpriteType(){
       return Sprite.SpriteType.BOX;
    }
}

最后:

public boolean isGameWon(Board board) {
    for (Point point : board.getTargetPoints())
        if(board.getTopSpriteAt(point).getSpriteType()!=SpriteType.BOX)
            return false;
    return true;
}

这样,您就可以解决必须在Sprite中为每个类型X创建一个isATypeX()方法的问题。 如果您需要新类型,则向枚举添加新值,只有需要检查此类型的规则才需要知道它。

答案 4 :(得分:1)

基本上,而不是

if (sprite instanceof Box)
    // Do something

使用

sprite.doSomething()

其中doSomething()Sprite中定义,并在Box中覆盖。

如果您希望将这些规则与Sprite类层次结构分开,可以将它们移动到单独的Rules类(或接口),其中Sprite具有getRules()方法,并且子类返回不同的实现。这将进一步提高灵活性,因为它允许相同Sprite子类的对象具有不同的行为。

答案 5 :(得分:1)

以下是您可能想要计算的每种类型的没有isAnX()方法的通用计数器的示例。假设您想要计算板上X型的数量。

public int count(Class type) {
    int count = 0;
    for (Point point : board.getTargetPoints())
        if(type.isAssignable(board.getTopSpriteAt(point)))
            count++;
    return count;
}

我怀疑你真正想要的是

public boolean isAllBoxes() {
    for (Point point : board.getTargetPoints())
        if(!board.getTopSpriteAt(point).isABox())
            return false;
    return true;
}

答案 6 :(得分:1)

你真正在这里测试的是,

  

玩家赢得游戏,而Sprite位于主席顶部吗?

因此,我建议这些名字:

public boolean isGameWon(Board board) {
    for (Point point : board.getTargetPoints())
        if(!board.getTopSpriteAt(point).isWinningSprite())
            return false;
    return true;
}

拥有isBox功能绝对没有意义。没有任何。您也可以使用instanceof

但如果BoxBottleTarget都是获胜的牌,那么你可以将它们全部归还

class Box {
    public override bool isWinningSprite() { return true; }
}

然后,您可以添加其他类型的“获胜”精灵,而无需更改isGameWon功能。

答案 7 :(得分:1)

如何使用访客。

每个点都继承了acceptBoxDetection方法:

boolean acceptBoxDetection(Visitor boxDetector){
           return boxDetector.visit(this);
}

and then Visitor does:

boolean visit(Box box){
        return true;
}

boolean visit(OtherStuff other){
        return false;
}

答案 8 :(得分:0)

关于面向对象设计/重构的一般陈述很难给予恕我直言,因为“最佳”行动非常依赖于上下文。

你应该尝试将“做某事”移动到Sprite的虚拟方法中,它什么都不做。可以在循环中调用此方法。

Box然后可以覆盖它并执行“某事”。

答案 9 :(得分:0)

我认为人们向你建议的是正确的。您的doSomething()可能如下所示:

class Sprite {
    public int doSomething(int cnt){
       return cnt;
    }
}

class Box extends Sprite {
    @Override
    public int doSomething(int cnt){
       return cnt + 1;
    }
}

因此,在您的原始代码中,您可以执行此操作:

int cnt = 0;
for (Point point : board.getTargetPoints()) {
    Sprite sprite = board.getTopSpriteAt(point)
    cnt = sprite.doSomething(cnt);
}

否则,您也可以使用您的建议实现相同的目标,但每个循环可能需要额外的计算。

class Sprite {
    public boolean isBox() {
        return false;
    }
}

class Box extends Sprite {
    @Override
    public boolean isBox(){
       return true;
    }
}

答案 10 :(得分:-1)

当你说'堆叠'和'最重要的一个'时,你能不能只拿最上面一个并用它做点什么?