You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

网球计分Kata中状态设计模式的正确应用方式咨询

嘿,这个问题问得特别好——网球计分的状态跳转确实是状态模式的经典场景,但因为你的上下文(TennisGame)核心职责是响应得分事件后返回当前比分,而非主动执行状态相关动作,所以需要调整状态模式的常规用法,让状态本身来主导得分后的转换逻辑。

下面我一步步拆解如何落地:

核心思路:让每个状态自己管自己的转换

不要用单一枚举来承载所有状态,而是给每个得分状态创建对应的类,实现同一个状态接口。每个状态类只需要负责两件事:

  1. 处理「当前选手得分」事件,返回下一个状态
  2. 返回当前状态的显示文本(比如"Love"、"Deuce")

这样一来,所有的条件判断会被分散到各个状态类中,而非堆在一处。

具体实现示例

1. 定义状态接口

先搭建最基础的状态契约:

public interface ScoreState {
    // 处理当前选手得分,返回转换后的新状态
    ScoreState onScore(Player currentPlayer, Player opponent);
    // 返回当前状态的可读文本
    String getDisplayText();
    // 可选:标记是否是比赛结束的终端状态
    default boolean isTerminal() {
        return false;
    }
}

2. 实现各个具体状态类

每个状态类只关注自己的转换规则:

  • 基础得分状态(Love/Fifteen/Thirty/Fourty)
public class LoveState implements ScoreState {
    @Override
    public ScoreState onScore(Player currentPlayer, Player opponent) {
        return new FifteenState();
    }

    @Override
    public String getDisplayText() {
        return "Love";
    }
}

public class FifteenState implements ScoreState {
    @Override
    public ScoreState onScore(Player currentPlayer, Player opponent) {
        return new ThirtyState();
    }

    @Override
    public String getDisplayText() {
        return "Fifteen";
    }
}

// ThirtyState和FourtyState逻辑类似,这里省略
  • Deuce状态
public class DeuceState implements ScoreState {
    @Override
    public ScoreState onScore(Player currentPlayer, Player opponent) {
        // Deuce时得分的选手进入Advantage状态
        return new AdvantageState(currentPlayer);
    }

    @Override
    public String getDisplayText() {
        return "Deuce";
    }
}
  • Advantage状态(需要记录占优选手)
public class AdvantageState implements ScoreState {
    private final Player advantagedPlayer;

    public AdvantageState(Player advantagedPlayer) {
        this.advantagedPlayer = advantagedPlayer;
    }

    @Override
    public ScoreState onScore(Player currentPlayer, Player opponent) {
        if (currentPlayer == advantagedPlayer) {
            // 占优选手再得分,直接赢下比赛
            return new GameState(currentPlayer);
        } else {
            // 对手得分,回到Deuce
            return new DeuceState();
        }
    }

    @Override
    public String getDisplayText() {
        return "Advantage " + advantagedPlayer.getName();
    }
}
  • Game状态(比赛结束)
public class GameState implements ScoreState {
    private final Player winner;

    public GameState(Player winner) {
        this.winner = winner;
    }

    @Override
    public ScoreState onScore(Player currentPlayer, Player opponent) {
        // 比赛结束后得分无效,保持当前状态
        return this;
    }

    @Override
    public String getDisplayText() {
        return "Game to " + winner.getName();
    }

    @Override
    public boolean isTerminal() {
        return true;
    }
}

3. 调整Player类

让Player持有ScoreState实例,而不是枚举。得分时直接调用当前状态的转换方法:

public class Player {
    private final String name;
    private ScoreState currentState;

    public Player(String name) {
        this.name = name;
        this.currentState = new LoveState(); // 初始状态为Love
    }

    public void score(Player opponent) {
        this.currentState = currentState.onScore(this, opponent);
    }

    public ScoreState getCurrentState() {
        return currentState;
    }

    public String getName() {
        return name;
    }
}

4. 简化TennisGame类

TennisGame只需要负责协调选手、响应得分事件,以及根据两个选手的当前状态生成比分字符串——完全不用处理状态转换逻辑:

public class TennisGame {
    private final Player player1;
    private final Player player2;

    public TennisGame(Player player1, Player player2) {
        this.player1 = player1;
        this.player2 = player2;
    }

    public void pointWonBy(String playerName) {
        if (playerName.equals(player1.getName())) {
            player1.score(player2);
        } else if (playerName.equals(player2.getName())) {
            player2.score(player1);
        }
        // 可添加非法输入的异常处理
    }

    public String getScore() {
        ScoreState state1 = player1.getCurrentState();
        ScoreState state2 = player2.getCurrentState();

        // 先判断比赛是否结束
        if (state1.isTerminal()) {
            return state1.getDisplayText();
        }
        if (state2.isTerminal()) {
            return state2.getDisplayText();
        }

        // 处理Deuce场景(双方都是Fourty)
        if (state1.getDisplayText().equals("Fourty") && state2.getDisplayText().equals("Fourty")) {
            return new DeuceState().getDisplayText();
        }

        // 普通比分场景,直接拼接
        return state1.getDisplayText() + " - " + state2.getDisplayText();
    }
}

这样做的好处

  1. 消除大量if-else:每个状态的转换逻辑都封装在对应类中,代码更清晰、易维护
  2. 符合开闭原则:如果要新增特殊规则(比如抢七),只需要新增对应的状态类,不用修改原有代码
  3. 职责分离:TennisGame只做协调和比分展示,状态转换的细节完全交给状态类处理,上下文职责更纯粹

内容的提问来源于stack exchange,提问作者Atul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:54:06