3
\$\begingroup\$

I want to write well-thought-out, professional, object-oriented code. I wrote this little game to practice this. My goal was to find out how to dissect a number guessing game object-oriented. In the following I present you my model. I ask you to check this model for thoroughness and quality. I will explain, step by step, how I came to formulate the individual program parts in this way, and not otherwise, and would ask you to point out any errors or imperfections of my object-oriented understanding.

My understanding of Objects

Objects in programming are used to depict facts or objects of the real world on the software level. An object has attributes and can do something with his attributes. On this idea my code will be based.

The Project

Before programming a project I have to clearly define, what I want to create. Keeping a long story short: I wanted to create a Game in which a Player has to Guess Numbers.

Detailed description and implementation

At first, there has to be a matrix respectively a surrounding, in which all of that will happen. I called the matrix Game.

In the game, there is the player and the "guess number game". Also here is stuff that is needed to interact with the game like a Scanner-object and other stuff.

Game.java

import java.util.Scanner;

public class Game {
    private Scanner input;
    private Player player;
    private GuessNumberGame guessNumberGame;
    private boolean running;
    private String command;

    Game() {
        this.player = new Player();
        this.input = new Scanner(System.in);
        this.guessNumberGame = new GuessNumberGame(20);
        this.running = true;
    }

    public void init() {
        while (running) {
            System.out.println("commands: leave, play");
            System.out.print(">> ");
            this.command = this.input.next();

            if (this.command.equals("leave")) {
                this.running = false;
            } else if (command.equals("play")) {
                this.player.play(this.input, this.guessNumberGame);
                this.guessNumberGame.setSecretNumber(20);

            } else {
                System.out.println("unknown command");
            }
        }
    }
}

The actual game (Not to be confused with my superior matrix, which is also called game, because the overall product is a game) Is the "guess number game". Here you can find the variables and the functions that are needed to play the guess number game.

GuessNumberGame.java

import java.util.Random;

public class GuessNumberGame {
    private int secretNumber;
    private Random random;

    GuessNumberGame(int limit) {
        this.random = new Random();
        this.setSecretNumber(limit);
    }

    public void setSecretNumber(int limit) {
        this.secretNumber = this.random.nextInt(limit);
    }

    public int getSecretNumber() {
        return this.secretNumber;
    }
}

The actual game logic is not implemented here because the player is the one who plays, not the game itself. That's why the game logic is found in the Player class.

Player.java

import java.util.Scanner;

public class Player {
    public void play(Scanner input, GuessNumberGame game) {
        int tries = 5;
        int number = 0;

        for (int i = 0; i < tries; i++) {
            System.out.print("guess number: ");
            number = Integer.parseInt(input.next());
            if (number > game.getSecretNumber()) {
                System.out.println("secret number is lower");
            } else if (number < game.getSecretNumber()) {
                System.out.println("secret number is higher");
            } else {
                break;
            }
        }
        if (number == game.getSecretNumber()) {
            System.out.println("WIN");
        }
        if (tries == 5 && number != game.getSecretNumber()) {
            System.out.println("You needed to many attempts!");
        }
    }
}

Finally, the game only needs to be started and initialized in a starter.

public class Starter {
    public static void main(String[] args) {
        Game game = new Game();
        game.init();
    }
}

I want to write a much more complex but textual role playing game in Java. But before I do so, I wanted to check with your help if my object oriented programming skills are good enough for that in this little example. What do you think of my understanding of the object oriented programming? I am interested in your thoughts!

\$\endgroup\$
  • 1
    \$\begingroup\$ I'd suggest that it's helpful to think of where changes can come from. If you want some users to have the display in console output, and another set of users wants to play the game via a user interface, or if you have British users who want a variation in the rules, or another set who wants the the language settings in chinese --- all of these will drive changes which will cause you to reconsider the objects you are using and will necessarily affect your design. But generally speaking you'll want the rules associated with the game to be separated from the display of the game. \$\endgroup\$ – BKSpurgeon Nov 18 '17 at 1:46
1
\$\begingroup\$

Architecture

Your usage of the class names Game and GuessNumberGame is misleading. One would either expect GuessNumberGame to be a Game, i. e. to extend it (the name implies it), or for it to be two different concepts. Since your Game class does nothing than just starting the GuessNumberGame, one might as well just use one class for the game and have it start itself.

Your Starter class as well does not add anything, so just move the main method into your Game class.

Another thing to consider is that your Player class does not have any state, i. e. no member variables that contain any information about the player instance. So you might consider yourself the player that acts upon the Game instance to play it. If you look what the play() method actually does, it is a single void method that actually contains logic that belongs to the game, not to the player itself. The player prompts "guess number: ", isn't that what you would expect the game to ask the player? The player then inputs a number (this is the part that makes sense), but then the player compares the two numbers. Isn't that the main game logic of a number guessing game? So it should be in the game class, too.

So, what should actually be not in the game class here? In this case, maybe nothing. Overcomplicating your architecture and dependencies between classes can hurt more than it helps in some cases, and this number guessing game is probably too simple for 4 classes.

But how do I decide what classes I need?

Objects [...] are used to depict facts or objects of the real world [...] I wanted to create a Game in which a Player has to Guess Numbers

Instead of (only) objects of the real world, one might want to think about concepts and use cases. You, the player, definitely are an entity. But do you need a class for that? If your class will not contain any state anyways, that might be an indication that your entity is not actually part of your system. You as a player are just using the program, i. e. calling the interface of the classes.

OOP Concepts

When programming in OOP, you usually want the classes to be as cleanly separated as possible. Every class represents one thing and one thing only, and it should have a well defined interface. That means that only those methods are public that should be called from the outside. Everything that can be private should be private. For example, the player should not ask the game for a number and compare it, but instead he/she should give the number to the game and wait for the answer. The game makes the rules, not the player, and the interface should hide the rules behind the scenes.

What could an interface for a number guessing game look like?

I would expect such a game to have a public method to start() the game, then I would expect to be prompted to input a number. So just by reorganizing the code you already have you can reduce the interface to just starting the game, because all the interaction is controlled by the game rules.

Naming and Coding Style

You are using this a lot. This is not needed at all in your case, and only decreases readability. You need this when you have parameters that have the same name as member variables.

For example:

private String name;

public void setName(String name) {
    this.name = name; // Avoiding the naming conflict.
}

But:

public String getName() {
    return name; // No conflict, "this" not needed.
}

This is just an example for a getter and setter, which brings us to the next point, variable and method naming. You used the concept of getters and setters, but somehow wrong, as getters and setters should simply access a variable, rarely do any additional calculations. Regarding your method setSecretNumber(int limit), one would expect the secret number to have the value limit afterwards. Instead, you are assigning a random number. A better name would be for example chooseRandomSecretNumber(int limit), and now the parameter name limit makes more sense as well.

Another thing is your usage of the init() method. This method does not actually initialize() the game, it just starts it. Where is the game initialized? In the constructor. So move the initialization code to a private method called initalize() (much better than just init()), and have the (public) constructor call the initialization method. The method that is now called init() can be called start(), and could probably be the only public method.

Putting it all together

This is an attempt to reorganize the code and improve it. Note that I did not run or test the code, because it is about the concepts, organization and style.

import java.util.Scanner;
import java.util.Random;

public class Game {
    private static final int RANDOMLIMIT;
    private static Random random;

    public Game() {
        random = new Random();
    }

    public static void main(String[] args) {
        // No Starter class needed. Game will start itself.
        Game game = new Game();
        game.start();
    }

    public void start() {
        String command; // Can be local.
        Scanner input = new Scanner(System.in);

        // Game loop. To end the game, just return or break.
        while (true) {
            System.out.println("commands: leave, play");
            System.out.print(">> ");

            command = input.next();

            if (command.equals("leave")) {
                return; // Escape the endless game loop.
            }

            if (command.equals("play")) {
                nextTurn(input);
            } else {
                System.out.println("unknown command");
            }
        }
    }

    private void nextTurn(Scanner input) {
        // The actual guessing logic.
        int tries = 5;
        int secretNumber = random.nextInt(RANDOMLIMIT);
        int inputNumber; // Making it more explicit.

        for (int i = 0; i < tries; i++) {
            System.out.println("guess number: ");
            inputNumber = input.nextInt(); // No parsing needed.

            if (inputNumber > secretNumber) {
                System.out.println("secret number is lower");
            } else if (inputNumber < secretNumber) {
                System.out.println("secret number is higher");
            } else {
                // Equal, guessed right.
                System.out.println("WIN");
                return; // Don't need to break, just return.
            }
        }

        // You had a bug here. You compared tries == 5,
        // but never changed it. Fortunately it didn't matter,
        // because after the loop you had used 5 tries anyways.
        System.out.println("You needed too many attempts!");

        // The return is optional, because the method ends here,
        // but it is more explicit.
        return;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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