Open/Closed Principle

After adding a new feature to a Rock, Paper, Scissors program I had been writing, I wrote this blog about why relying on concretions rather than abstractions makes things very difficult and is generally not a good idea.

In the blog post I provide different examples of me relying on concretions such as various hard coded strings used for interaction with the user. Though the examples are all of different parts in my code, they all stem from the same problem: I violated the Open/Closed Principle.

I have pondered the Open/Closed Principle before but it’s not until it stared me in the face and made every instance of where I needed to change my code problematic, that I think I finally understood why violating it is such an issue.

The Open/Closed principle states that code should be open for extension but closed for modification. But what does that mean?

I will try and explain it by using my code as an example. My program was littered with hardcoded strings so when I received a new feature to add the option for the player to play in another language, each hardcoded string was a pain point because I had made the concrete assumption that English will be the only language playable.

Concretions certainly don’t bode well for extension of the behaviour. In my case the behaviour that I need to extend was communication to the user. I had closed down the option of extending my program’s ability to communicate something in a different language.

public void askUserForGameMode() {        
output.println("Enter '1' for Human vs. Human...");
}
public void askForMove(String player) {
output.println(String.format("%s pick your move..."));
}

So, what should I have done in the first place? The answer is simple, instead of hard coding values throughout my code, I should have had thought of what each english string represented in my program. That would have lead me to the fact it represents either a greeting, an instruction or a request. If I had then thought a step further I may have deduced that all of these sentences could be represented as one concrete thing; english language.

At the very least, these should all be on the class English. I went ahead and created this class then created methods for each interaction that returned the strings I needed which were previously hardcoded.

package main.game;

public class English {

public String promptMode() {
return "Enter '1' for Human vs. Human...";
}

public String promptMove(String player) {
return (String.format("%s pick your move ..."));
}
}

However, this didn’t get rid of the problem. Instead of using hardcoded strings, I initialised the Game class with an instance of English and passed this instance to the methods which still limited my code to english, it was not flexible.

public Game(UI inputOutput, Rules rules, English englishLanguage) {
this.inputOutput = inputOutput;
this.rules = rules;
this.english = englishLanguage;
}

I was still relying on the concrete instead of the abstract with english being a concrete implementation of a language.

public void announceWinner(String winningMove) {
if (winningMove.equals("draw")) {
output.println(english.announceDraw());
} else {
output.println(english.announceWin(winningMove));
}
}

What I needed was something that encompassed both english and greek, the abstraction of both of those is language. I then created a language Interface then English and Greek classes that implemented that interface. I would then be relying on this abstraction rather than the concrete implementation.

public void askUserForGameMode(Language language) {
output.println(language.promptMode());
}

public void askForMove(Language language, String player) {
output.println(language.promptMove(player));
}

So I’d be passing the idea of a language to each method and would only change the code in one place (the main method that is the main entry point to my application where I pass in the user’s language choice) instead of several places throughout the program.

In the example below you can see where I store the user’s choice of language in a variable called… *drumroll* … languageChoice!

public Language getLanguage() {
HashMap<String, Language> languages = new HashMap<>();
languages.put("1", new English());
languages.put("2", new Greek());
    String languageChoice = askUserForLanguageSelection();    
return languages.get(languageChoice);
}

Instead of passing language to each method that uses it, I have chosen to initialise my Game class with the knowledge of a language; whichever one the user chooses.

package main.game;
public class GameRunner {
public static void main(String[] args) {
CommandLine ui = new CommandLine(System.out, System.in);
Rules rules = new Rules();
Language language = ui.getLanguage();
Game newGame = new Game(ui, rules, language);
        newGame.runGame(language, gameMode);
}
}

In the example above, you can see that the Game class becomes initialised with knowledge of the ui, rules and language.

Now that the Game class has knowledge the language, I can use the abstraction of language and call my methods on the one I need:

Before:
public void askUserForGameMode() {
output.println("Enter '1' for Human vs. Human")
}

After:
public void askUserForGameMode() {
output.println(language.promptForMode);
}

Now that the language interface is being used instead of hardcoded values, the code’s behaviour can be extended, it is open. My game can be playable in any language I want. The only thing I need to do is to add another language to my languages hashmap without modifying other methods in my code, this part of the code is closed.