网球计分Kata中状态设计模式的正确应用方式咨询
嘿,这个问题问得特别好——网球计分的状态跳转确实是状态模式的经典场景,但因为你的上下文(TennisGame)核心职责是响应得分事件后返回当前比分,而非主动执行状态相关动作,所以需要调整状态模式的常规用法,让状态本身来主导得分后的转换逻辑。
下面我一步步拆解如何落地:
核心思路:让每个状态自己管自己的转换
不要用单一枚举来承载所有状态,而是给每个得分状态创建对应的类,实现同一个状态接口。每个状态类只需要负责两件事:
- 处理「当前选手得分」事件,返回下一个状态
- 返回当前状态的显示文本(比如"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(); } }
这样做的好处
- 消除大量if-else:每个状态的转换逻辑都封装在对应类中,代码更清晰、易维护
- 符合开闭原则:如果要新增特殊规则(比如抢七),只需要新增对应的状态类,不用修改原有代码
- 职责分离:
TennisGame只做协调和比分展示,状态转换的细节完全交给状态类处理,上下文职责更纯粹
内容的提问来源于stack exchange,提问作者Atul
相关产品推荐
相关产品推荐

