Tôi nghĩ vấn đề ở đây là bạn chưa đưa ra một mô tả rõ ràng về các nhiệm vụ sẽ được xử lý bởi các lớp nào. Tôi sẽ mô tả những gì tôi nghĩ là một mô tả tốt về những gì mỗi lớp nên làm, sau đó tôi sẽ đưa ra một ví dụ về mã chung minh họa các ý tưởng. Chúng ta sẽ thấy rằng mã ít được ghép nối hơn và do đó nó không thực sự có các tham chiếu tròn.
Hãy bắt đầu với việc mô tả những gì mỗi lớp làm.
Các GameStatelớp học chỉ nên chứa các thông tin về tình trạng hiện tại của trò chơi. Nó không nên chứa bất kỳ thông tin nào về những trạng thái trong quá khứ của trò chơi hoặc những động thái trong tương lai là có thể. Nó chỉ nên chứa thông tin về những quân cờ trên ô vuông nào, hoặc bao nhiêu và loại cờ nào trên những điểm nào trong backgammon. Các GameStatesẽ phải chứa một số thông tin thêm, như thông tin về castling trong cờ vua hay về khối tăng gấp đôi trong backgammon.
Các Movelớp học là một chút khéo léo. Tôi sẽ nói rằng tôi có thể chỉ định di chuyển để chơi bằng cách chỉ định GameStatekết quả từ việc chơi di chuyển. Vì vậy, bạn có thể tưởng tượng rằng một động thái chỉ có thể được thực hiện như một GameState. Tuy nhiên, trong khi đi (ví dụ) bạn có thể tưởng tượng rằng việc chỉ định di chuyển sẽ dễ dàng hơn rất nhiều bằng cách chỉ định một điểm duy nhất trên bảng. Chúng tôi muốn Movelớp học của chúng tôi đủ linh hoạt để xử lý một trong hai trường hợp này. Do đó, Movelớp thực sự sẽ là một giao diện với một phương thức thực hiện bước di chuyển trước GameStatevà trả về một bước di chuyển mới GameState.
Bây giờ RuleBooklớp có trách nhiệm biết mọi thứ về các quy tắc. Điều này có thể được chia thành ba điều. Nó cần phải biết những gì ban đầu GameStatelà gì, nó cần phải biết những động thái nào là hợp pháp và nó cần phải có khả năng để biết nếu một trong những người chơi đã giành chiến thắng.
Bạn cũng có thể tạo một GameHistorylớp để theo dõi tất cả các động thái đã được thực hiện và tất cả những GameStatesđiều đã xảy ra. Một lớp mới là cần thiết bởi vì chúng tôi đã quyết định rằng một người GameStatekhông nên chịu trách nhiệm về việc biết tất cả những GameStategì xuất hiện trước nó.
Điều này kết luận các lớp / giao diện tôi sẽ thảo luận. Bạn cũng có một Boardlớp học. Nhưng tôi nghĩ rằng các bảng trong các trò chơi khác nhau đủ khác nhau để khó có thể nhìn thấy những gì có thể được thực hiện chung với các bảng. Bây giờ tôi sẽ tiếp tục cung cấp các giao diện chung và triển khai các lớp chung.
Đầu tiên là GameState. Vì lớp này hoàn toàn phụ thuộc vào trò chơi cụ thể, không có Gamestategiao diện chung hoặc lớp.
Tiếp theo là Move. Như tôi đã nói, điều này có thể được biểu diễn bằng một giao diện có một phương thức duy nhất có trạng thái trước khi di chuyển và tạo ra trạng thái sau khi di chuyển. Đây là mã cho giao diện này:
package boardgame;
/**
*
* @param <T> The type of GameState
*/
public interface Move<T> {
T makeResultingState(T preMoveState) throws IllegalArgumentException;
}
Lưu ý rằng có một tham số loại. Điều này là do, ví dụ, một ChessMoveý chí cần phải biết về các chi tiết của việc di chuyển trước ChessGameState. Vì vậy, ví dụ, khai báo lớp ChessMovesẽ là
class ChessMove extends Move<ChessGameState>,
nơi bạn đã xác định một ChessGameStatelớp.
Tiếp theo tôi sẽ thảo luận về RuleBooklớp học chung . Đây là mã:
package boardgame;
import java.util.List;
/**
*
* @param <T> The type of GameState
*/
public interface RuleBook<T> {
T makeInitialState();
List<Move<T>> makeMoveList(T gameState);
StateEvaluation evaluateState(T gameState);
boolean isMoveLegal(Move<T> move, T currentState);
}
Một lần nữa có một tham số loại cho GameStatelớp. Vì RuleBookphải biết trạng thái ban đầu là gì, chúng tôi đã đặt một phương thức để đưa ra trạng thái ban đầu. Vì RuleBookcần phải biết những động thái nào là hợp pháp, chúng tôi có các phương pháp để kiểm tra xem một động thái đó có hợp pháp ở một quốc gia nhất định hay không và đưa ra một danh sách các động thái hợp pháp cho một quốc gia nhất định. Cuối cùng, có một phương pháp để đánh giá GameState. Lưu ý rằng RuleBookchỉ nên chịu trách nhiệm mô tả nếu một hoặc những người chơi khác đã giành chiến thắng, nhưng không phải là người ở vị trí tốt hơn ở giữa trò chơi. Quyết định ai ở vị trí tốt hơn là một điều phức tạp nên được chuyển vào lớp của chính nó. Do đó, StateEvaluationlớp học thực sự chỉ là một enum đơn giản được đưa ra như sau:
package boardgame;
/**
*
*/
public enum StateEvaluation {
UNFINISHED,
PLAYER_ONE_WINS,
PLAYER_TWO_WINS,
DRAW,
ILLEGAL_STATE
}
Cuối cùng, hãy mô tả GameHistorylớp học. Lớp này có trách nhiệm ghi nhớ tất cả các vị trí đã đạt được trong trò chơi cũng như các động tác được chơi. Điều chính nó có thể làm là ghi lại Movenhư đã chơi. Bạn cũng có thể thêm chức năng cho hoàn tác Moves. Tôi có một thực hiện dưới đây.
package boardgame;
import java.util.ArrayList;
import java.util.List;
/**
*
* @param <T> The type of GameState
*/
public class GameHistory<T> {
private List<T> states;
private List<Move<T>> moves;
public GameHistory(T initialState) {
states = new ArrayList<>();
states.add(initialState);
moves = new ArrayList<>();
}
void recordMove(Move<T> move) throws IllegalArgumentException {
moves.add(move);
states.add(move.makeResultingState(getMostRecentState()));
}
void resetToNthState(int n) {
states = states.subList(0, n + 1);
moves = moves.subList(0, n);
}
void undoLastMove() {
resetToNthState(getNumberOfMoves() - 1);
}
T getMostRecentState() {
return states.get(getNumberOfMoves());
}
T getStateAfterNthMove(int n) {
return states.get(n + 1);
}
Move<T> getNthMove(int n) {
return moves.get(n);
}
int getNumberOfMoves() {
return moves.size();
}
}
Cuối cùng, chúng ta có thể tưởng tượng làm một Gamelớp để gắn kết mọi thứ lại với nhau. Đây Gamelớp có nghĩa vụ phải tiếp xúc với các phương pháp mà làm cho nó có thể cho mọi người thấy những gì hiện nay GameStatelà, xem ai, nếu có ai có một, xem những gì di chuyển có thể được chơi, và chơi một di chuyển. Tôi có một triển khai dưới đây
package boardgame;
import java.util.List;
/**
*
* @author brian
* @param <T> The type of GameState
*/
public class Game<T> {
GameHistory<T> gameHistory;
RuleBook<T> ruleBook;
public Game(RuleBook<T> ruleBook) {
this.ruleBook = ruleBook;
final T initialState = ruleBook.makeInitialState();
gameHistory = new GameHistory<>(initialState);
}
T getCurrentState() {
return gameHistory.getMostRecentState();
}
List<Move<T>> getLegalMoves() {
return ruleBook.makeMoveList(getCurrentState());
}
void doMove(Move<T> move) throws IllegalArgumentException {
if (!ruleBook.isMoveLegal(move, getCurrentState())) {
throw new IllegalArgumentException("Move is not legal in this position");
}
gameHistory.recordMove(move);
}
void undoMove() {
gameHistory.undoLastMove();
}
StateEvaluation evaluateState() {
return ruleBook.evaluateState(getCurrentState());
}
}
Lưu ý trong lớp này rằng RuleBookkhông chịu trách nhiệm cho việc biết hiện tại GameStatelà gì. Đó là GameHistorycông việc của. Vì vậy, các Gamecâu hỏi về GameHistorytình trạng hiện tại là gì và cung cấp thông tin này cho RuleBookkhi nào Gamecần nói các động thái pháp lý là gì hoặc nếu có ai giành chiến thắng.
Dù sao, quan điểm của câu trả lời này là một khi bạn đã xác định hợp lý về trách nhiệm của mỗi lớp và bạn làm cho mỗi lớp tập trung vào một số ít trách nhiệm và bạn giao mỗi trách nhiệm cho một lớp duy nhất, sau đó là các lớp có xu hướng được tách rời và mọi thứ trở nên dễ dàng để viết mã. Hy vọng rằng đó là rõ ràng từ các ví dụ mã tôi đã đưa ra.
RuleBooklấy ví dụStatenhư là một đối số và trả về giá trị hợp lệMoveList, tức là "đây là nơi chúng ta đang ở, điều gì có thể được thực hiện tiếp theo?"