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?