I'm writing a Java class to manage a hex map (class GameMapImpl implements GameMap
) that contains Cell
objects. Cell objects are saved in a HashMap<Hex,Cell>
, where the key is the position on the hex map.
The whole thing used to be very tightly coupled between cells and GameMap, with a cyclic dependency between the two, but I'm trying to refactor it to allow more easy testing.
(code below is simplified)
public interface GameMap {
void hasCell(Hex hex);
void getCell(Hex hex);
}
class GameMapImpl implements GameMap
{
private Map<Hex, Cell> cellMap;
GameMapImpl(Set<Cell> cells) {
cellMap = new HashMap<>();
for (Cell cell : cells) {
// check some conditions on the cells
// ensure the map is correct, e.g. only one cell
// of a particular type
// ensures that the cellMap key is always the cell's position.
cellMap.put(cell.position(), cell);
}
}
//obvious implementation for hasCell and getCell methods
}
Each Cell
needs to have a Hex position
field so it can look up its own position in the GameMap
. In addition, Cell
objects need to have a reference to the owning GameMap
, to perform common useful operations such as looking for their neighbours.
public class Cell {
final Hex position;
final GameMap gameMap;
Cell(Hex position, GameMap gameMap) {
this.position = position;
this.gameMap = gameMap;
}
public Set<Cell> getNeighbours() {
//perform operation that needs both gameMap and position
}
}
The GameMap
is built in a GameMapBuilder
, which provides a Set<Cell>
to the GameMap
constructor.
public class GameMapBuilder {
public GameMap build(...) { // args omitted for simplicity
Set<Cells> cells = new HashSet<>();
for (... : ...)
{
Hex position = calculatePosition(...);
Cell cell = new Cell(position, gameMap);
}
GameMap gameMap = new GameMap(cells);
return gameMap;
}
}
As you can probably see, the last snippet of code is wrong, because i'm referencing a non-existent gameMap
variable. Here lies my problem: if I create the GameMap
after initializing the cells, I cannot pass it in the Cell
's constructor. If I do the opposite, I cannot pass the Set<Cells>
to the gameMap
in order to initialize correctly.
Does any more experienced programmer have an idea on how to decouple correctly these two classes?
Alternatively, I can go back to the previous tightly coupled design and just assume that Cells exist only when a GameMap creates them. Being these small objects and contained in the same package, it wouldn't be that big of a deal.
Use setter for passing
GameMap
intoCell
not constructor. You can make it package-protected to hide it from other code and call setter inGameMap
constructor or in another loop inGameMapBuilder
. All known DE-frameworks use setters to solve circular dependencies.