【问题标题】:Is it OK to have an empty method?有一个空方法可以吗?
【发布时间】:2015-06-01 16:21:55
【问题描述】:

是否可以有一个空方法并在其子类中覆盖它? 这就是它在我的代码中的样子。

public class Rook() {

    public void voidCastleRight() { }

}

public class ShortRook() extends Rook {

    @Override
    public void voidCastleRight() {
        getPlayer().setkSC(false); //void King Side Castling (Short Castle)
    }

}

public class LongRook() extends Rook {

    @Override
    public void voidCastleRight() {
        getPlayer().setqSC(false); //void Queen Side Castling (Long Castle)
    }
}

上下文是一个国际象棋引擎。它是用 Java 编写的,它必须寻找下一个“最佳”动作来确定棋盘的状态。因此,尽可能高效地实现一切非常重要,因为许多方法将被调用数百万次。因此,我想要这种 Rooks 层次结构,而不是一个 Rook 类,在该类中我必须检查 Rook 在哪一侧并检查 Rook 是否在其初始位置等。

第一次创建 Board 时,会有一个 ShortRook 和一个 LongRook。随着游戏的进行,由于 Pawn 的提升,可能会在游戏中引入更多的 Rook。这些将是 Rook 的实例化。

每当移动 Rook 时,都会调用方法 voidCastleRight()。由于 Pawn 提升而存在的 Rooks 不应在移动时立即使城堡无效(清空方法)。从游戏开始就存在的 Rooks 在移动时应该使城堡权利无效(子类中的方法)。

我还编写了一个解析器,它接受 FENStrings 并将它们转换为 Board,反之亦然。当 Rooks 不在其初始位置时,无法区分 Short-Rook 和 LongRook。这不是问题,因为城堡的权利已经无效,它们可以被解析为 Rook 的实例。因此,如果我在 Rook 使相关城堡权利无效(即它已移动)时将 Short- 或 LongRook 对象投射到 Rook 上是否可以?这样一来,它就不会在城堡已经无效时不必要地无效了。我不关心这些解析器方法的复杂性,因为它们不会用于搜索。

虽然有些人可能会认为这些想法是微优化“这是万恶之源”,但当方法必须被调用几百万次时,这些优化可能会得到回报。我也更关心 OOP 范式。

PS:我知道 Java 不是用于此应用程序的最佳语言,它无关紧要。我知道对象创建(在 Java 中)很昂贵。我会确保在搜索过程中没有创建任何对象。

【问题讨论】:

  • 当然,为什么不呢。此外,在 Java 中创建对象并不是很昂贵。
  • 我同意。 Java 中的对象创建并不昂贵。你不妨说 Java 很贵。
  • Rook 可以直接实例化还是必须继承?在字母大小写中,您可以将其抽象化。
  • 我知道 Java 不是用于此应用程序的最佳语言,它无关紧要。我知道对象创建(在 Java 中)很昂贵。我会确保在搜索过程中没有创建任何对象看起来你生活在 10 年前或更长时间。
  • 随机附注(如果您仍在处理此问题)- 将 voidCastleRight() 重命名为 disableCastleRight 或类似名称,因为 void 是 Java 中的关键字。这不会引发错误,但会降低可读性...

标签: java oop subclass hierarchy chess


【解决方案1】:

虽然有一个什么都不做的空方法当然是可以的,但你应该评估它的替代方案 - 一个完全缺失的空方法,即一个 abstract 方法。

这可能适用于您的情况,因为您将创建ShortRookLongRook 或提升的Rook

public abstract class AbstractRook() {
    public abstract void voidCastleRight();
}

public class ShortRook() extends AbstractRook{
    @Override
    public void voidCastleRight() {
        getPlayer().setkSC(false); //void King Side Castling (Short Castle)
    }
}

public class LongRook() extends AbstractRook{
    @Override
    public void voidCastleRight() {
        getPlayer().setqSC(false); //void Queen Side Castling (Long Castle)
    }
}

public class PromotedRook() extends AbstractRook{
    @Override
    public void voidCastleRight() {
        throw new IllegalStateException("Promoted rook cannot castle");
    }
}

【讨论】:

  • ...由于 Pawn 的推广,游戏中可能会引入更多的 Rooks - 我想这意味着在这种情况下将创建一个 Rook
  • @OldCurmudgeon 你说得对,OP 正在谈论获得一辆既不长也不短的车。我认为最好为这样的 rooks 定义一个第三类,以明确地捕获这样的 rooks 不能城堡的规则。
  • 虽然我习惯使用抽象类,但我没有想到我可以创建一个PromotedRook 类来处理Pawn 对Rooks 的提升,而不是Rook 类的实例化。我认为您的答案是我实施的正确方法。谢谢。
【解决方案2】:

问题标题的答案:

有空方法可以吗?

是一个响亮的 - 这是完全可以接受的情况。

但是 - 我觉得您使用了错误的工具来满足您的要求。我不认为禁用castling 的功能应该是Rooks 功能的一部分。您需要的是一个观察者/听众,可以观察车的移动并在正确的情况下禁用castling。然后,您可以使用类似的架构来启用/禁用以 En-Passant 为例。

还要记住Castling 只有在国王从未移动过相关的车从未移动过方格的情况下,才可能进行攻城所涉及的国王和车之间空置国王没有被阻止,并且国王没有越过或结束于它会被阻止的方格。您可以使用 rooks、kings 和 move 引擎上的侦听器在一个地方实施整个规则。

【讨论】:

  • 我知道必须满足什么条件才能城堡。问题主要是关于移动的 Rook 如何影响游戏状态。这与移动生成或棋子如何移动无关。但是,我对您所说的这些“观察者/听众”感兴趣。你有什么参考资料可以阅读更多关于它们的信息吗?
  • @Auberon - 您正在寻找观察者模式。
【解决方案3】:

我会考虑一个不做任何事情的非抽象方法是code smell

这里的第一个选择是创建一个 Rook 接口而不是一个类。

如果您想为基类提供具体功能,则可以考虑第二种选择,可能定义您引用的 getPlayer() 方法,然后使用抽象类并将该方法标记为抽象。

与您的问题无关,但我会考虑您的代码的其他两个方面。首先是在你的方法中使用名称“void”,这有点尴尬。

最后,我通常会告诉人们重新考虑使用 void 函数。原因是 void 函数,根据定义,什么都不做或执行side effect。副作用本身并不坏,但由于它涉及修改状态,它会使你的程序更难推理。

例如,考虑一个方法validMoves(),它返回棋子可以进行的有效移动的列表。在这种情况下,您可以只拥有一个Piece 接口,并在评估可能的移动时将每个部分都视为相同:

interface Piece {
    List<Move> validMoves();
}

在这种情况下,您将只有一个 Rook 实例类;一个在国王的两边。您不必为 Kings、Rooks 和 Pawns 定义特殊方法,因为它们每个人都知道自己可以做出的动作。

这将大大扁平化您的类层次结构,并使处理和评估板更容易推理。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-27
    • 2011-09-27
    • 2019-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多