I'm building a small game similar to chess. I'd like to be able to reuse the structure for another version of checkers too. I'm modeling the games with interfaces (showing only relevant ones):
RuleOrchestrator.class
public interface RuleOrchestrator {
Collection<Move> allowedMoves(Player player, Move move);
}
Player.class
public interface Player {
Move getMove(Board board);
}
AIEngine.class
public interface AIEngine {
Move computeBestMove(Board board, RuleOrchestrator ruleOrchestrator);
}
The general design seems to be fine for both games. However, I find myself doing quite a few castings. After thinking about it, I have come to what looks like the root of the issue: I'm using the interfaces only to define the general workflow of the games, but many times the implementations are not interchangeable. The two implementations of AIEngine
work with any Board
and RuleOrchestrator
, but almost every other concrete class works only with the concrete classes in their own "realm" (game).
Chess.class
public class Chess implements Game {
private final Player player1;
private final Player player2;
private final RuleOrchestrator ruleOrchestrator;
private final Board board;
public Chess(String player1, String player2, int size) {
this.player1 = new HumanPlayer(player1);
this.player2 = new HumanPlayer(player2);
this.board = new ChessBoard(size);
this.ruleOrchestrator = new ChessOrchestrator(board); // 1
}
// ...
}
ChessOrchestrator.class
public class ChessOrchestrator implements RuleOrchestrator {
private final Board board; // 2
public ChessOrchestrator(Board board) {
this.board = board;
}
@Override
public Collection<Move> allowedMoves(Player player, Move move) {
// Do stuff
... ((ChessBoard)board).getKing(); // 3
ChessMove chessMove = (ChessMove) move; // 4
// More things
// ...
}
}
For instance, ChessOrchestrator
expects to work with a chess board. It needs chess-specific info and calls methods specific to ChessBoard
. It probably doesn't make sense to define the field at // 2 with type Board
. Using ChessBoard
would avoid having to cast. Then, I would need o either work directly with ChessBoard
also in Chess
, or casting in line // 1. Furthermore, move
also needs to be cast to a ChessMove
at // 4.
Not only I dislike these casts, but I'm afraid they're the symptom of a bigger problem. Is this a code smell? How can I avoid these castings and improve the app's design?