3

我最近一直在从事一个使用通用最佳搜索算法的 Java 项目。为了使算法本身通用,我在算法中使用的所有类上使用了一堆泛型。

这是所有的类/接口声明:

public class StateSearch<E extends AbstractState<E>>
public class AbstractState<E> implements State<E>
public class StateNode<E extends AbstractState<E>> implements Comparable<StateNode<E>>
public class StateQueue<E extends AbstractState<E>>
public interface State<E> extends ComparableState<E>>

现在理论上,所有这些都可以正常工作。但是,当我应用算法时,我计划将“E”作为某种游戏状态(比如棋盘、纸牌游戏、探路者、你有什么)。因此,我创建了以下类:

public class GameState extends AbstractState<String>

我对这个特定设置的意图是让 GameState 成为代表特定游戏状态的字符串的容器。但是,这会导致尝试使用以下代码创建 StateSearch 时出现问题:

new StateSearch<GameState>(new GameState(initialState), new GameState(goalState));

我最终得到一个绑定不匹配错误,指出这GameState不是<E extends AbstractState<E>>. 现在,我相信我明白为什么会这样了。我的想法是因为GameStateextends AbstractState<String>,而不是AbstractState<E>

不幸的是,我不希望我的GameState类有一个通用类型。它旨在实现实际做某事,因此从泛型切换到字符串。例如,我可能想GameState在一个完全不同的项目中创建一个类,该项目使用整数来实现它。

鉴于我在类中使用和执行的方法、变量和操作GameState,它不能是通用的。

毕竟我的问题是:有没有办法将我的GameState类实现为非泛型类,以满足我的类提出的边界要求StateSearch

我不介意是否必须更改某些类声明。关键是我需要 Algorithm 是Generic,并且 Implementation 是Non-GenericGameState班级需要成为我在两者之间过渡的点。


编辑:
我需要GameState类作为某个游戏的实现。和接口旨在让我无需实际实现即可构建状态搜索算法AbstractStateState以这种方式进行设置允许我在多个游戏中应用此设置。

该类StateSearch基本上只是将状态从我的 中拉出StateQueue,这只是一个AbstractState按优先级系统排序的类向量(因此是 Comparable 实现)。扩展状态树的方式是使用类中的getNextState()函数AbstractState。这很好,但该getNextState()函数基本上是从我的getSuccessors()函数中获取最佳状态,该状态只能GameStateClass 中定义。在其他地方,它只是抽象的。

我无法getSuccessors()以通用方式定义函数,因为它纯粹基于实现。此函数旨在从当前状态返回所有可能的状态。例如,如果游戏是井字游戏,该getSuccessors()函数将返回一个游戏状态列表,其中每个状态代表一个可能的移动。

该类GameState还包含 a getEstimatedCost()getRunningCost()它基本上分别充当h(x)g(x)函数。这也只能GameState类中定义,因为两者都取决于实现。

4

3 回答 3

3

您定义事物的方式实际上是Curiously recurring 模板模式的实现

当你有:

public class StateSearch<E extends AbstractState<E>>

这意味着要使用 aGameState作为其通用类型,它需要定义为:

public class GameState extends AbstractState<GameState>

至于您真正需要什么,尚不清楚为什么需要泛型。为什么GameState接口不够用?

于 2014-02-02T04:52:41.090 回答
1

搜索算法需要两件事:

  1. 数据(连接图)
  2. 一种比较数据的方法(图中的节点)

每个节点都知道它的连接节点,代表一个图。并且节点彼此可比。这满足了搜索算法需要的两件事。

因此,在我们的设计中,节点将这些信息提供给搜索算法是很自然的。所有其他节点状态都应该对搜索算法隐藏,以保持良好的 OO 原则(封装。)

由于这些原因,我建议重构这个更简单的设计。

//Comparable interface satisfies measuring a node's value.
//getAdjacentNodes abstract method exposes graph structure.
abstract class StateNode implements Comparable<StateNode>{
    abstract StateNode[] getAdjacentNodes();
}

//StateSearch can perform any search algorithm it needs StateNode.
class StateSearch<E extends StateNode>

//StateQueue is probably unnecessary. Consider replacing with a PriorityQueue.
class StateQueue<E extends StateNode> queue;

是否StateNode使用内部状态是实现代码的决定。在下面的示例中,GameNode确实保持游戏状态,但搜索算法对此一无所知。它所知道的只是它需要知道的最低限度;什么StateNode暴露。如果我完全忽略了状态,搜索算法将不受影响。

public class GameNode extends StateNode{
    String state;
    public GameNode(String state){
        this.state = state;
    }

    public StateNode[] getAdjacentNodes(){
        //return adjacent nodes.
    }

    public String getGameState(){
        return state;
    }
    public int compareTo(StateNode other){
        //comparison code
    }
}
于 2014-02-02T22:05:47.937 回答
-3

对我来说,Java Generic 机制通过使集合库成为可能提供了它的大部分好处。它似乎是针对那种用例开发的——这充分证明了它的合理性——它只是管理它。如果您查看集合转换的实现,您可能会看到编译警告或注释以关闭警告,因为无法创建通用数组。请注意,因为它是通过类型擦除而不是代码生成来工作的,所以它保证不会出现代码膨胀,但不会像在 C++ 中那样提供额外的效率——尽管您确实受益于一些关于在运行时不会发生类型转换异常的保证。当我使用它时,我通常会尝试将其用于类似于集合的东西,并尝试将它们作为模型。

我怀疑不可能用泛型做所有你想做的事情,我会小心翼翼地尝试做太多事情——就像我对任何你不经常使用的语言特性一样,以达到反射水平的熟练程度。现在需要你一个小时才能完成的东西,当你稍后回来修改或扩展该代码时,你会花费更长的时间来理解。

于 2014-02-02T05:55:25.940 回答