Java类与方法组织及SOLID原则:井字棋项目类职责咨询
Hey Nicole, great question—let's break this down clearly while sticking to your 5-class constraint and SOLID principles. It's totally normal to get confused about responsibility boundaries when starting out, so let's map out exactly where each piece of logic belongs.
First, let's lock in what each of your 5 classes should do, no exceptions:
1. Driver Class (Entry Point + Coordinator)
This is the "conductor" of your game—its job is to start the program, wire up all components, and manage the game's flow. It should not handle UI rendering or low-level move logic, but it will orchestrate everything else.
- What it does:
- Hosts the
main()method to launch the app. - Holds the single source of truth for the game board state (e.g., a
char[][]array). - Creates instances of your GUI and chosen computer player (Strategic or Dumb, based on user selection).
- Listens for user input from the GUI, triggers game rule checks, and tells the computer player to make a move when it's their turn.
- Hosts the
2. GUI Class (Pure View + User Input Handler)
This class should only care about pixels and user clicks—no game rules allowed. Violating this breaks the Single Responsibility Principle (the "S" in SOLID), because mixing UI logic with game logic makes your code harder to modify (e.g., switching to JavaFX later would force you to rewrite game rules too).
- What it does:
- Renders the board, displays turn status, and shows win/tie messages.
- Listens for user clicks on board cells, then passes the clicked coordinates to the Driver (via a callback method like
onCellClicked(int row, int col)). - Provides methods for the Driver to update the UI (e.g.,
updateBoard(char[][] boardState)orshowWinner(String message)).
3. AbstractComputerPlayer (Strategy Contract)
This is your blueprint for all computer players—it defines a common interface so the Driver doesn't need to care about which strategy is being used.
- What it does:
- Declares an abstract method like
int[] makeMove(char[][] currentBoard)that all subclasses must implement (returns the row/column of the computer's next move). - Can include shared utility methods (e.g.,
isCellEmpty(char[][] board, int row, int col)) to avoid code duplication between subclasses.
- Declares an abstract method like
4. StrategicComputerPlayer & DumbComputerPlayer (Strategy Implementations)
These classes exist solely to implement their specific move logic. They only care about the current board state passed to them—they don't know anything about the game's overall flow or UI.
- What they do:
- DumbComputerPlayer: Picks a random empty cell (simple, straightforward).
- StrategicComputerPlayer: Implements logic to block your wins, pursue its own wins, etc. Note: This can include mini win-checks for strategy purposes, but it doesn't handle the global "game over" check—that's the Driver's job.
Win Check Logic
Global win/tie checks (to determine if the game should end) belong in the Driver Class. Here's why:
- It's part of game flow management, which is the Driver's responsibility.
- Putting it in the GUI would couple UI and business logic (bad for SOLID).
- Putting it in computer players would force them to handle game state they don't need to know about.
You can implement this as a private helper method in the Driver, like:
private boolean checkForWin(char[][] board, char player) { // Check rows, columns, diagonals for 3-in-a-row for (int i = 0; i < 3; i++) { if (board[i][0] == player && board[i][1] == player && board[i][2] == player) return true; if (board[0][i] == player && board[1][i] == player && board[2][i] == player) return true; } return (board[0][0] == player && board[1][1] == player && board[2][2] == player) || (board[0][2] == player && board[1][1] == player && board[2][0] == player); }
Computer Move Calls
The Driver should trigger the computer's move—right after validating the user's move and confirming the game isn't over. Here's the flow:
- User clicks a cell → GUI sends coordinates to Driver.
- Driver updates the board with the user's move.
- Driver checks if the user won or tied. If not:
- Driver calls
computerPlayer.makeMove(board)to get the computer's move coordinates. - Driver updates the board with the computer's move.
- Driver checks if the computer won or tied, then tells the GUI to refresh.
public class TicTacToeDriver { private char[][] board; private TicTacToeGUI gui; private AbstractComputerPlayer computerPlayer; public static void main(String[] args) { // Let user choose strategy here—example uses Strategic AbstractComputerPlayer player = new StrategicComputerPlayer(); new TicTacToeDriver(player); } public TicTacToeDriver(AbstractComputerPlayer computerPlayer) { this.board = new char[3][3]; initializeEmptyBoard(); this.computerPlayer = computerPlayer; // Pass a callback to the GUI for user clicks this.gui = new TicTacToeGUI(this::handleCellClick); gui.updateBoard(board); } private void handleCellClick(int row, int col) { if (board[row][col] == '\0') { // User's turn board[row][col] = 'X'; gui.updateBoard(board); if (checkForWin(board, 'X')) { gui.showWinner("You win! 🎉"); return; } if (isBoardFull(board)) { gui.showWinner("It's a tie! 🤝"); return; } // Computer's turn int[] computerMove = computerPlayer.makeMove(board); int compRow = computerMove[0]; int compCol = computerMove[1]; board[compRow][compCol] = 'O'; gui.updateBoard(board); if (checkForWin(board, 'O')) { gui.showWinner("Computer wins! 😅"); return; } } } // Helper methods for win check and board fullness go here }
This setup keeps every class focused on its job, follows SOLID, and makes your code easy to modify or extend later (e.g., adding a new computer strategy just requires a new subclass of AbstractComputerPlayer).
内容的提问来源于stack exchange,提问作者Nicole Velos

