I'm trying to come up with a design for a chess engine in Kotlin that hides implementation details, and that ensures that such an implementation cannot accidentally become spaghetti.
Client side example code
I envision that a client would access the chess engine with something like:
package chessprototype
import chessprototype.chess.Chess
class ChessClient {
fun hello() {
val searcher = Chess.mateSearcher()
searcher.board = "k1K/2Q w"
val move = searcher.findBestMove()
println("Best move is " + move.toString())
}
}
To achieve that I created the following.
Main interface code
package chessprototype.chess
// Undesired cyclic dependency needed to expose the MateSearcher
import chessprototype.chess.searchers.MateSearcher
/** Understandable interface for the client */
class Chess {
interface Move {
override fun toString(): String
}
data class TextMove(val text: String): Move { override fun toString() = text }
interface Searcher {
var board: String
fun findBestMove(): Move
}
companion object {
fun mateSearcher() = MateSearcher()
}
}
I've put all interfaces that a client would use in a facade-like Chess
object.
And I've put basic access types in there as well, such as TextMove
I've put a factory method in the Chess
object as well to expose the complicated MateSearcher
while hiding its implementation.
I've put it in a companion object
so that I can create it without instantiating a Chess
class.
Implementation code
package chessprototype.chess.searchers
// Undesired cyclic dependency needed for
// - basic types (Move, TextMove)
// - the interface to implement (Searcher)
import chessprototype.chess.Chess
class MateSearcher: Chess.Searcher {
override var board: String = ""
override fun findBestMove(): Chess.Move {
return doComplicatedSearch()
}
// Complicated code that should not be exposed to the client
private fun doComplicatedSearch(): Chess.Move { return Chess.TextMove("Qb7#") }
}
I've put the implementation in a separate package so that:
- it cannot accidentally use code that was not explicitly imported first.
- it cannot accidentally allow other code to use implementation details.
As you can see there is a mutual import that seems undesired to me.
My challenges
- Ensure that the Searcher cannot 'accidentally' start using low level implementations from other parts of the code.
- Ensure that the main interface cannot 'accidentally' leak implementation details.
- Ensure that the main interface for the client is as clean and simple as possible.
My immediate problem: how can I eliminate the cyclic dependency of packages?
And more generally, what would you do differently and why?