是否可以在循环中的每个方法之后检查条件?如果是这样,如何?

Is it possible to check a condition after every method in a loop? If so, how?

就像我在标题中所说的那样,我在制作关于高中的角色扮演游戏中有一个循环。这是设置您的一天以按时间顺序执行各个序列的主循环。我的问题是我怎样才能检查 boolean "beat" 或 boolean "lost" (指的是游戏状态)是否已被触发为真在循环中的每个方法之后,但仍将这些方法保持在一个循环中。在我的 while 循环中嵌套 if 语句是唯一的方法吗?

while (!g.getBeat() || g.getLost()) 
{
    g.wakeUp();
    g.goToSchool();
    g.beforeLunch();
    g.lunchActivity();
    g.afterLunch();
    g.afterSchool();
    g.home();
    g.sleep();
}

您必须手动完成。为了帮助您少写一些代码,制作一个检查这两个条件的方法:

private boolean stopTheLoop() {
    return g.getBeat() || g.getLost();
}

现在通过在每个方法之后进行检查将循环转换为无限循环:

while (true) {
    g.wakeUp();
    if (stopTheLoop()) break;
    g.goToSchool();
    if (stopTheLoop()) break;
    g.beforeLunch();
    if (stopTheLoop()) break;
    ...
}

您可以通过引入状态来使用 switch 语句:

int state = 0;
while (!g.getBeat() || g.getLost()) 
{
    switch (state) {
    case 0:
      g.wakeUp();
      break;
    case 1: 
      g.goToSchool();
      break;
    case 2: 
      g.beforeLunch();
      break;
    case 3: 
      g.lunchActivity();
      break;
    case 4: 
      g.afterLunch();
      break;
    case 5: 
      g.afterSchool();
      break;
    case 6: 
      g.home();
      break;
    case 7: 
      g.sleep();
      break;
    default:
      // some error handling, depending on your logic, 
      // or perhaps state = -1 to restart
    }
    state++;
}

如果您想像在示例中那样按照自己的方法保留每个步骤,您将无能为力...

如果满足停止循环的条件,将所有这些方法设为 return "true",则可以减少代码量...但是,如果您计划,这可能是不可能的在顺序上下文中使用这些方法。

if (!g.getBeat() || g.getLost()) do {
    if (g.wakeUp()) break;
    if (g.goToSchool()) break;
    ...
    if (g.sleep()) break;
} while (true);

一个可能的技巧是让这些方法在满足停止条件时抛出异常。然后你会在循环外捕获那个异常。这样你就可以保存 if (...) break 语句。然而,这不是一个好的做法。

if (!g.getBeat() || g.getLost()) {
  try {
    do {
      g.wakeUp();
      g.goToSchool();
      ...
      g.sleep();
    } while (true);
  } catch (ActivityLoopFinished ex) {
    // nothing to do here
  }
}

没有任何 "built-in" 方法可以做到这一点,但通过一些编码,一切皆有可能。

首先,无论你如何处理,我都会将结束条件包装到一个方法中,只是为了让事情更方便:

public class Game {
    // method, members, etc...

    public boolean isOver() {
        return !getBeat() || getLost();
    }
}

现在,想到的第一个选项是手动执行此操作:

while (!g.isOver()) {
    g.wakeUp();
    if (g.isOver()) {
        break;
    }
    g.goToSchool();
    if (g.isOver()) {
        break;
    }
    // etc...
}

但这涉及到很多代码,并且不太优雅。

或许,一种更面向对象的方法是在处理程序中扭曲每个此类调用 class:

public abstract GameStageHandler (Game g) {
    protected Game g;

    public GameStageHandler (Game g) {
        this.g = g;
    }


    /**
     * Play a stage in the game
     * @return Whether the game should go on or not after this stage
     */
    public boolean play() {
        performStage();
        return !g.isOver();
    }

    public abstract void performStage();
}

并在游戏的每个阶段实施。例如。对于 wakeUp() 阶段,您将拥有:

public abstract WakeUpHandler (Game g) {
    public WakeUpHandler (Game g) {
        super(g);
    }

    @Override
    public void performStage() {
        g.wakeUp();
    }
}

然后,在 main 方法中,您可以拥有一组这样的处理程序,并迭代它们:

List<GameStageHandler> handlers = ...;
while (!g.isOver()) {
    for (GameStageHandler handler : handlers) {
        if (!g.play()) {
            break;
        }
     }
}

这可能超出了您的作业范围,因为您注意到 class 甚至 Runnable 还没有涵盖。不过,这是一个有趣的问题,挑战在于想出一种简洁而优雅的方式来表示它,同时尽可能避免重复。这是一个使用 Java8 和函数式编程技术的解决方案。

第一个洞察是每个游戏动作或步骤都可以表示为 lambda 表达式或方法引用。我假设您有 Game class。每个这样的步骤都将一个 Game 实例作为参数(或接收者),因此可以输入为 "consumer" 个 Game 实例。因此我们可以将它们放入一个数据结构中:

List<Consumer<Game>> actions = Arrays.asList(
    Game::wakeUp,
    Game::goToSchool,
    Game::beforeLunch,
    Game::lunchActivity,
    Game::afterLunch,
    Game::afterSchool,
    Game::home,
    Game::sleep);

现在我们将它们放在数据结构中,我们可以遍历它们:

for (Consumer<Game> action : actions) {
    action.accept(game);
}

当然,我们想在每个动作后检查游戏是否结束。假设您在 Game class 上有一个方法 isOver 检查正确的终止条件。然后你可以这样做:

for (Consumer<Game> a : actions) {
    a.accept(game);
    if (game.isOver()) {
        break;
    }
}

一天的比赛只 运行 秒。大概你想 运行 游戏无限期地直到它达到它的终止条件。为此你需要一个外循环,并且终止检查必须跳出外循环:

outer:
while (true) {
    for (Consumer<Game> a : actions) {
        a.accept(game);
        if (game.isOver()) {
            break outer;
        }
    }
}

这本身就足够了:您有一个游戏动作列表,以及一个 运行 无限循环,在每个动作后检查终止条件。

但是等等,还有更多!这里仍然有相当多的样板文件,可以使用 Java 8 的一些流功能将其消除。考虑到可以使用 noneMatch 方法针对谓词测试流的每个元素。当其中一个谓词 returns 为真时,此方法终止。

因为每个动作都有类型Consumer<Game>,我们需要一个小的辅助函数将每个动作变成一个谓词:

static Predicate<Consumer<Game>> stepAndCheck(Game game) {
    return c -> { c.accept(game); return game.isOver(); };
}

现在我们可以运行一天的所有动作如下:

actions.stream().noneMatch(stepAndCheck(game))

无限期地 运行 游戏,我们只需将其包装在一个 while 循环中。因为 noneMatch returns true 如果,正如它所说, none 谓词匹配,我们将其作为循环条件并将循环体留空:

while (actions.stream().noneMatch(stepAndCheck(game))) {
    // nothing
}

这似乎不必要地晦涩难懂。事实上,对于像这样的玩具示例,它可能是。然而,对于更复杂的问题,像这样的技术是非常有价值的。