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 GameState
lớ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 GameState
sẽ 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 Move
lớ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 GameState
kế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 Move
lớ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 đó, Move
lớ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 GameState
và trả về một bước di chuyển mới GameState
.
Bây giờ RuleBook
lớ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 GameState
là 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 GameHistory
lớ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 GameState
không nên chịu trách nhiệm về việc biết tất cả những GameState
gì 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 Board
lớ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ó Gamestate
giao 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 ChessMove
sẽ là
class ChessMove extends Move<ChessGameState>
,
nơi bạn đã xác định một ChessGameState
lớp.
Tiếp theo tôi sẽ thảo luận về RuleBook
lớ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 GameState
lớp. Vì RuleBook
phả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ì RuleBook
cầ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 RuleBook
chỉ 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 đó, StateEvaluation
lớ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ả GameHistory
lớ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 Move
như đã chơi. Bạn cũng có thể thêm chức năng cho hoàn tác Move
s. 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 Game
lớp để gắn kết mọi thứ lại với nhau. Đây Game
lớ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 GameState
là, 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 RuleBook
không chịu trách nhiệm cho việc biết hiện tại GameState
là gì. Đó là GameHistory
công việc của. Vì vậy, các Game
câu hỏi về GameHistory
tình trạng hiện tại là gì và cung cấp thông tin này cho RuleBook
khi nào Game
cầ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.
RuleBook
lấy ví dụState
như 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?"