4

我正在用单元格编写简单的游戏,这些单元格可以处于免费和被玩家采取两种状态。

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

它很丑。如何做到这一点以避免这种instanceof hacks?我不是在谈论另一个 hack,而是想找到合适的 OOP 解决方案。

4

8 回答 8

5

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

Cell 对象将使用组合而不是继承。换句话说,一个 Cell 会有一个 current state 属性。当您有一个当前状态属性为 FreeState 对象的单元时,它就是一个空闲单元。当你有一个当前状态属性是 TakenState 对象的 Cell 时,它就是一个自由状态。

如何做到这一点以避免这种instanceof hacks?

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

于 2012-11-16T22:14:01.503 回答
4

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

当一个以前空闲的单元被占用时,你现在怎么办?创建一个具有相同坐标的新对象并丢弃旧对象?但从概念上讲,它仍然是同一个单元格!(或者是否可以同时存在具有相同 x 和 y 的空闲单元和占用单元?)

从 OOP 的角度来看,您应该有一个具有“已采用”属性的单元类,或者正如另一个答案所建议的那样,“所有者信息”。如果您觉得无论出于何种原因这不应该成为单元类的一部分,那么如何将所有者信息单独保存在一个Map<Cell,Owner>?

于 2012-11-16T22:13:28.230 回答
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`
  }

这可能对您的确切目的有利也可能不利,但它是一个干净简单的设计。

于 2012-11-16T22:06:43.623 回答
1

我认为这是使用Factory PatternAbstract Factory Pattern的好地方。

工厂模式返回几个(产品层次结构)子类(如 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 
...

}

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

在此处输入图像描述

链接:

抽象工厂模式

工厂模式

于 2012-11-16T22:01:22.527 回答
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 好多少

于 2012-11-16T22:07:07.560 回答
0

你可以有两个集合并在它们被占用时将单元格从一个移动到另一个?例如,在开始时,您将拥有freeSet完整的单元格和takenSet空的单元格。当单元格被占用时,它们会从 freeSet 移动到 takeSet。如果您在 TakenCell 和 FreeCell 之上有一个界面,您可以使用相同的界面键入每个集合。

或者...

FreeCell查看您对and的定义会很有帮助,TakenCell但我认为您可以将它们建模为具有可空字段的相同对象,该字段被填充以指示它已被采用。然后,您可以对同一类使用两个集合类型。

于 2012-11-16T22:00:21.017 回答
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); 
于 2012-11-16T22:18:46.107 回答
0

你可以使用访客模式,这些 FreeCell 和 TakenCell 应该实现

可访问的界面

interface Visitable {
    void accept(Visitor visitor);
}

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

在执行Visitor's visit(FreeCell freecCell) 方法时将是:

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

在Visitor的visit(TakenCell takeCell)方法的实现中什么都不会

和两个类:FreeCell 和 TakenCell,在方法 accept(Visitor visitor) 中应该有:

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

在 for 循环中你应该有:

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

someConcreteVisitor 是访问者的实现者的实例。

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

于 2014-07-24T20:35:03.607 回答