我正在用简单的游戏编写游戏,游戏可以分为两种状态 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解决方案。
答案 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如果你有一个超类和一些子类, 并根据提供的一些数据,你必须 返回其中一个子类的对象。
链接:
答案 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之上有一个接口,则可以使用相同的接口键入每个集合。
...替代地
查看FreeCell
和TakenCell
的定义会很有帮助,但我认为您可以将它们建模为具有可空字段的相同对象,该字段将填充以表明它已被拍摄。然后,您可以将两种类型用于同一类。
答案 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。