I'm in the process of working through a kotlin example project. One of the tasks is to implement a game board class that can track coordinates.
The provided interfaces look like:
interface SquareBoard {
val width: Int
fun getCellOrNull(i: Int, j: Int): Cell?
fun getCell(i: Int, j: Int): Cell
fun getAllCells(): Collection<Cell>
}
interface GameBoard<T> : SquareBoard {
operator fun get(cell: Cell): T?
operator fun set(cell: Cell, value: T?)
}
My implemented classes look like this:
open class SquareBoardImpl( override val width: Int) : SquareBoard {
private val cells:List<Cell>
init {
val coords = (1..width).toList()
fun pairs(i: Int) = coords.withIndex().map { j -> Cell(i, j.value) }
this.cells = coords.flatMap(::pairs)
}
override fun getCellOrNull(i: Int, j: Int): Cell?
{
return cells.find { cell-> cell == Cell(i,j) }
}
override fun getCell(i: Int, j: Int): Cell {
return getCellOrNull(i, j) ?: throw IllegalArgumentException()
}
override fun getAllCells(): Collection<Cell>
{
return cells;
}
}
class GameBoardImpl<T>(override val width: Int) : SquareBoardImpl(width), GameBoard<T>
{
var cellMap:MutableMap<Cell, T?> = getAllCells().associateWith { null }.toMutableMap()
override fun get(cell: Cell): T? {
return cellMap[cell]
}
override fun set(cell: Cell, value: T?) {
cellMap[cell] = value
}
}
fun createSquareBoard(width: Int): SquareBoard = SquareBoardImpl(width)
fun <T> createGameBoard(width: Int): GameBoard<T> = GameBoardImpl<T>(width)
Everything compiles and runs, but I'm seeing the following warning in the IDE inside the init for SquareBoardImpl:
Accessing non-final property width in constructor
For this simple example, I know that I could simply ignore the warning, but I would like to know what the proper way is to handle this. How can I refer to 'width' inside the constructor in a safe way?
The problem here is that subclasses of SquareBoardImpl
can override the width
property. If they do, the body of the width
accessor will be executed for only partially initialized supertype, which is not expected in most cases.
From your code it doesn't seem you actually need width
to remain open, so I suggest making it final:
open class SquareBoardImpl(final override val width: Int) : SquareBoard { ... }
class GameBoardImpl<T>(width: Int) : SquareBoardImpl(width), GameBoard<T> { ... }