2
\$\begingroup\$

I was trying to solve Tennis-Refactoring-Kata. This seems quite hard at startup but, through use os OOP it can be deeply refactoring using just a single if.

My first understanding was about the fact that TennisGame (the main class) has a Score. Or at least should have one.

Furthermore score possibilities are just twenty (Love-All, Fifteen-All and so on...) and each one leads further Scores based on who is the last player who scores a point (e.g.: Love-All leads to Fifteen-Love if last point has been scored by first player, Love-Fifteen otherwise). From this observation I wrote 20 classes like this:

public class LoveAll implements IScore {

    public IScore Player1Scored() {
        return new FifteenLove();
    }

    public IScore Player2Scored() {
        return new LoveFifteen();
    }

    public String Label() {
        return "Love-All";
    }
}

This should seems a loss of time but this is not bad as it seems since each class is simple and concise. This give me the possibilities to write a TennisGame4 like this:

public class TennisGame4 implements TennisGame {    
    private IScore score = null; // Composition here - TennisGame has a Score
    private String player1Name;
    private String player2Name;

    public TennisGame4(String player1Name, String player2Name) {
        this.player1Name = player1Name;
        this.player2Name = player2Name;
        score = new LoveAll();
    }

    public void wonPoint(String playerName) {
        if (playerName.equals("player1"))
            score = score.Player1Scored();
        else 
            score = score.Player2Scored();
    }

    public String getScore() {
        return score.Label();
    }
}

Just a single if to distinguish who is the last player who score a point. What do you think about this solution? Does this solution respect the kata constraint?

Consider that:

  • Code surely can be enhanced (Player should be an object too);
  • To stay in the Kata I have do everything in 90 minutes or so;
\$\endgroup\$
2
  • 2
    \$\begingroup\$ It doesn't seem to make sense to have a class for each possible score combination. Why not a Score class with properties that is/are the score(s)? \$\endgroup\$
    – radarbob
    Commented Aug 30, 2017 at 15:16
  • \$\begingroup\$ What you've implemented is actually a finite state machine, albeit in a weird and verbose way. Each "score" correspond to a state. "Player1 scores" and "player2 scores" are the two events causing state transition. From my experience, state machine is often better expressed as plain data(e.g. strings or enum) than type hierarchy. \$\endgroup\$
    – Yizhe Sun
    Commented May 11, 2020 at 9:19

1 Answer 1

2
\$\begingroup\$

As you say, there are only a limited possibilities for a Score, which I would call GameScore to be more precise. This calls for an enum.

public enum GameScore {
    S0000(0, 0), S1500(15, 0), S3000(30, 0), ...;

    private static final GameScore[] SWIN = { S1500, ... };
    private static final GameScore[] RWIN = { S0015, ... };
    private static final String[] NAME = { "Love-All", ... };

    ...

    public GameScore serverWins() {
        return SWIN[ordinal()];
    }

    public GameScore returnerWins() {
        return RWIN[ordinal()];
    }

    public String getDisplayText() {
        return NAME[ordinal()];
    }
}

That's all. All in one class, ready for testing.

By the way, in Java all method names start with a lowercase letter, not an uppercase. (That would be C# or Ada.)

Using an enum and predefined string literals has the benefit of generating zero memory allocations at runtime.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.