简体   繁体   中英

Breaking cyclic dependency in constructor

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.

I think the problem here is that you use a builder. Basically what you did in the builder is took code away from the GameMap constructor. If you were to put this code in the GameMap constructor you can give this to the Cell constructor.

If you still want to keep the builder code separated from the GameMap code you can make a static method on the builder that the GameMap constructor calls and pass this (the GameMap object) as a parameter.

Use setter for passing GameMap into Cell not constructor. You can make it package-protected to hide it from other code and call setter in GameMap constructor or in another loop in GameMapBuilder . All known DE-frameworks use setters to solve circular dependencies.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM