Sto costruendo un piccolo gioco simile agli scacchi. Mi piacerebbe poter riutilizzare la struttura anche per un'altra versione di dama. Sto modellando i giochi con le interfacce (mostrando solo quelli rilevanti):
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);
}
Il design generale sembra andare bene per entrambi i giochi. Tuttavia, mi trovo a fare parecchi casting. Dopo averci pensato, sono giunto a ciò che sembra la radice del problema: sto usando le interfacce solo per definire il flusso di lavoro generale dei giochi, ma molte volte le implementazioni non sono intercambiabili. Le due implementazioni di AIEngine
funzionano con qualsiasi Board
e RuleOrchestrator
, ma quasi ogni altra classe concreta funziona solo con le classi concrete nel proprio "regno" (gioco).
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
// ...
}
}
Ad esempio, ChessOrchestrator
si aspetta di lavorare con una scacchiera. Ha bisogno di informazioni specifiche sugli scacchi e chiama metodi specifici per ChessBoard
. Probabilmente non ha senso definire il campo in // 2 con tipo Board
. L'utilizzo di ChessBoard
eviterebbe di dover eseguire il cast. Quindi, avrei bisogno o di lavorare direttamente con ChessBoard
anche in Chess
, o casting in linea // 1. Inoltre, move
deve anche essere castato a ChessMove
su // 4.
Non solo non mi piacciono questi cast, ma temo che siano il sintomo di un problema più grande. È un odore di codice? Come posso evitare questi casting e migliorare la progettazione dell'app?