Come posso progettare correttamente cosa dovrebbe fare un metodo o una funzione?
Per chiarire la domanda, ho intenzione di utilizzare un esempio di un semplice gioco di hangman (non GUI) che ho creato. Il design è semplice, ha una classe HangmanGame che ha la parola che l'utente deve indovinare e altre cose come i metodi che controllano il gioco. Ha anche una classe HangmanDriver che controlla il flusso del gioco e le chiamate ai metodi e così via.
1 dei metodi è quello di indurre gli utenti a indovinarlo e processarlo e lo fa chiamando 2 altri metodi privati (helper) all'interno della classe. Quello che segue è il codice (dopo il quale è la mia effettiva richiesta).
/**
* This method gets and processes a users guess.
*/
public void getAndProcessGuess()
{
this.processGuess(this.getGuess());
}
/**
* Asks the user for their guess and validates that their
* guess is legal and has not already been made.
*
* @return char The letter the user guessed
*/
private char getGuess()
{
java.util.Scanner input = new java.util.Scanner(System.in);
String guess = "";
char letter = '/**
* This method gets and processes a users guess.
*/
public void getAndProcessGuess()
{
this.processGuess(this.getGuess());
}
/**
* Asks the user for their guess and validates that their
* guess is legal and has not already been made.
*
* @return char The letter the user guessed
*/
private char getGuess()
{
java.util.Scanner input = new java.util.Scanner(System.in);
String guess = "";
char letter = '%pre%';
//get and validate the user input
while (true)
{
System.out.print("Please enter your guess: ");
try
{
guess = input.nextLine().toUpperCase();
}
catch (InputMismatchException e)
{}
System.out.println();
if (guess.length() >= 1)
letter = guess.charAt(0);
if (!(guess.matches("[A-Z]{1}")))
System.out.println("Please enter a single letter between A and Z\n");
else if(isLetterGuessed((letter)))
System.out.println("\nThat letter has already been guessed\n");
else
return letter;
printSeparator(); //if the guess isn't valid print out a line separator
}
}
/**
* Processes the guess from user
*
* @param letter the letter the user guessed
*/
private void processGuess(char letter)
{
if (secretWord.indexOf(letter) >= 0)
{
int count = secretWord.length() - secretWord.replaceAll(Character.toString(letter), "").length();
System.out.println("The letter " + letter + " appears " + count + (count == 1 ? " time." : " times.") + "\n");
setLetterAsGuessed(letter);
printSecretWordWithGuessedLettersShown();
}
else
{
System.out.println("The letter " + letter + " does not appear in the hidden word\n");
decrementNumOfGuessesLeft();
System.out.println("You have " + numOfGuessesLeft + " incorrect guesses left\n");
}
printSeparator();
}
';
//get and validate the user input
while (true)
{
System.out.print("Please enter your guess: ");
try
{
guess = input.nextLine().toUpperCase();
}
catch (InputMismatchException e)
{}
System.out.println();
if (guess.length() >= 1)
letter = guess.charAt(0);
if (!(guess.matches("[A-Z]{1}")))
System.out.println("Please enter a single letter between A and Z\n");
else if(isLetterGuessed((letter)))
System.out.println("\nThat letter has already been guessed\n");
else
return letter;
printSeparator(); //if the guess isn't valid print out a line separator
}
}
/**
* Processes the guess from user
*
* @param letter the letter the user guessed
*/
private void processGuess(char letter)
{
if (secretWord.indexOf(letter) >= 0)
{
int count = secretWord.length() - secretWord.replaceAll(Character.toString(letter), "").length();
System.out.println("The letter " + letter + " appears " + count + (count == 1 ? " time." : " times.") + "\n");
setLetterAsGuessed(letter);
printSecretWordWithGuessedLettersShown();
}
else
{
System.out.println("The letter " + letter + " does not appear in the hidden word\n");
decrementNumOfGuessesLeft();
System.out.println("You have " + numOfGuessesLeft + " incorrect guesses left\n");
}
printSeparator();
}
Cosa si dovrebbe fare all'interno di ciascuno di questi metodi e se stanno facendo troppo?
Il problema che ho con il modo in cui è ora è che i metodi stampano le cose. Funziona nel modo in cui il programma funziona, ma sembra che questi metodi stiano facendo più di quanto dovrebbero e che forse la stampa dovrebbe essere fatta dalla classe Driver non dall'istanza della classe. Inoltre, se questo dovesse mai essere modificato in una GUI, il metodo non avrebbe molto senso. Il problema di non avere il metodo di stampare una riga è che la convalida dovrebbe essere eseguita anche nella classe del driver.
Il modo in cui lo vedo è che ci sono tre percorsi possibili:
Inheritance
Il gioco dell'impiccato può essere trasformato in una superclasse e quindi avrà 2 figli uno HangmanConsoleGame e un altro HangmanGUIGame. Questo risolverebbe il problema con il modo in cui i metodi funzionano in ogni classe (ma la domanda rimane ancora se i metodi dovrebbero stampare cose).
Mantieni come è
Funziona per come funziona ora il gioco. Se dovessero essere apportate delle modifiche o se il gioco verrà modificato in una versione della GUI, il codice potrà essere nuovamente rielaborato in un secondo momento in modo che funzioni per le modifiche. Ciò presuppone che i metodi di validazione e stampa siano ok.
Refactor Now
Modifica il codice ora in modo che la convalida venga eseguita all'interno della classe Driver e i metodi restituiscano semplicemente i valori con indicazioni se il valore non è valido. Con la classe Driver che esegue tutte le operazioni di stampa e i metodi per indurre gli utenti a indovinare solo i valori di ritorno, può essere facilmente modificata in una GUI in un secondo momento e riduce al minimo le operazioni eseguite dai metodi.
Quale di questi ha più senso se esiste? C'è un modo migliore per farlo?