在这种情况下如何避免instanceof运算符

时间:2012-11-16 21:53:54

标签: java oop design-patterns polymorphism

我正在用简单的游戏编写游戏,游戏可以分为两种状态 Free Taken

interface Cell {
    int posX();
    int posY();
}

abstract class BaseCell implements Cell {

    private int x;
    private int y;

    public int posX() {
        return x;
    }

    public int posY() {
        return y;
    }

    ...
}

class FreeCell extends BaseCell {
}

class TakenCell extends BaseCell {
    private Player owningPlayer

    public Player owner() {
        return owningPlayer;
    }

}

在每个回合中,我需要检查所有细胞以使用以下方法计算下一个细胞状态

// method in class Cell
public Cell nextState(...) {...}

并收集(在Set)所有尚未拍摄的细胞。上面的方法返回Cell,因为单元格可能会从Free变为Taken或相反。 我正在做类似下面的事情来收集它们:

for (Cell cell : cells) {
    Cell next = cell.futureState(...);
    if(next instanceof FreeCell) {
        freeCells.add(currentCell);
    }
    ...
}

这太丑了。如何做到这一点,以避免这样的hacks实例?我不是在谈论另一个黑客,而是想找出适当的OOP解决方案。

8 个答案:

答案 0 :(得分:5)

听起来你正在和"State" pattern调情,但你并不在那里。使用状态模式,您将拥有Cell对象和“Cell State”类的层次结构。

Cell对象将使用合成而不是继承。换句话说,Cell将具有当前状态属性。如果你有一个Cell,其中currentState属性是一个FreeState对象,那么它就是一个空闲单元格。如果你有一个Cell,其中currentState属性是TakenState对象,那么它是一个自由状态。

  

如何避免这种黑客攻击?

每当您遇到需要执行instanceof的情况时,都会向Cell类添加一个方法并调用它。 Cell委托给当前状态。委托给当前状态的Cell中的代码实际上并不知道状态是什么。它只相信国家会做正确的事情。在FreeState和TakenState中,您提供了基于状态执行正确操作的每种方法的实现。

答案 1 :(得分:4)

我认为这里的设计问题是你有两个不同的类,它们基本上可以是同一个单元格的两个不同状态。

当以前的免费细胞被占用时你现在做什么?创建一个具有相同坐标的新对象并丢弃旧的对象?但它在概念上仍然是相同的细胞! (或者可以同时存在一个自由单元格和一个具有相同x和y的单元格吗?)

从OOP的角度来看,你应该有一个属性为“take”的单元类,或者另一个anwer建议的“所有者信息”。如果您认为这不应该出于任何原因而成为单元格类的一部分,那么如何将所有者信息分开在Map<Cell,Owner>

答案 2 :(得分:3)

好的,这是你可以采取的另一种方法。

 public class Cell {

     private int x;
     private int y;
     private OccupationInfo occupationInfo;

     public int posX() {
         return x;
     }

     public int posY() {
        return y;
     }

     public OccupationInfo getOccupationInfo() {
        return occupationInfo;
     }

     public boolean isFree() {
        return occupationInfo == null;
     }
  }

然后......

  public class OccupationInfo {
      private Player owningPlayer;
      // any other data you would've put in `TakenCell`
  }

这可能会或可能不会对您的确切目的有利,但它设计简洁明了。

答案 3 :(得分:1)

我认为这是使用工厂模式抽象工厂模式的好地方。

工厂模式返回几个实例(产品 层次结构)子类(如FreeCell,TakenCell等),但调用代码不知道实际的实现类。
调用代码调用接口上的方法,例如FreeCell,并使用多态,调用正确的doSomething()方法。

而不是使用instanceof(如切换),您可以调用相同的方法,但每个类将根据本地覆盖实现它。这个 在许多框架中都是一个非常强大和通用的功能。

而不是写:

for (Cell cell : cells) {
Cell next = cell.futureState(...);
if(next instanceof FreeCell) {
    freeCells.add(currentCell);
}
...
}

您可以输入:

for (Cell cell : cells) {
Cell next = cell.futureState(...);
 cell.doSomething(); // and no matter what class is FreeCell or TakenCell 
...

}

工厂模式返回其中一个 几个产品子类。你应该使用工厂 pattern如果你有一个超类和一些子类, 并根据提供的一些数据,你必须 返回其中一个子类的对象。

enter image description here

链接:

Abstract Factory pattern

Factory pattern

答案 4 :(得分:1)

您可以向Cell接口添加方法,以告知该单元格是否空闲:

interface Cell {
    int posX();
    int posY();
    boolean isFree();
}

class FreeCell extends BaseCell {
    public boolean isFree() { return true; }
}

class TakenCell extends BaseCell {
    private Player owningPlayer

    public boolean isFree() { return false; }

    public Player owner() {
        return owningPlayer;
    }
}

但我不认为这比使用instanceof

好多了

答案 5 :(得分:0)

你可以拥有两个套装,并在拍摄时将细胞从一个移动到另一个吗?例如,在开头你会有freeSet个完整的单元格而takenSet为空。当细胞被采集时,它们从freeSet移动到takeSet。如果您在TakenCell和FreeCell之上有一个接口,则可以使用相同的接口键入每个集合。

...替代地

查看FreeCellTakenCell的定义会很有帮助,但我认为您可以将它们建模为具有可空字段的相同对象,该字段将填充以表明它已被拍摄。然后,您可以将两种类型用于同一类。

答案 6 :(得分:0)

您的代码并不难看,它读得很好,它清楚地表达了应用程序逻辑。

单个instanceof测试通常不值得担心;它的用途很常见。 “标记接口”由instanceof测试。你的例子是一种标记界面。

并且instanceof非常快,显然JVM发现非常有效地优化它。

然而,一系列instanceof测试可能是问题的表现。添加一个警卫以确保枚举完成。

if(o instanceof A)
    ...
else if(o instanceof B)
    ...
else if ...
...
else // huh? o is null or of unknown type
    throw new AssertionError("unexpected type: "+o); 

答案 7 :(得分:0)

你可以使用Visitor Pattern,这些FreeCell和TakenCell应该实现

  

可访问界面

interface Visitable {
    void accept(Visitor visitor);
}

interface Visitor {
    void visit(FreeCell freeCell);
    void visit(TakenCell takenCell);
}

在访问者访问(FreeCell freecCell)方法的实现中将是:

public void visit(FreeCell freeCell) {
    freeCells.add(freeCell);
}

在访问者访问(TakenCell takenCell)方法的实现中将无所不用

和两个类:FreeCell和TakenCell,方法接受(访问者访问者)应该有:

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

并在for循环中你应该:

for (Cell cell : cells) {
Cell next = cell.futureState(...);
next.accept( someConcreteVisitor )
...
}

someConcreteVisitor是Visitor的实现者的实例。

这个for循环所在的类也可以是Visitable。