打破构造函数中的循环依赖

Breaking cyclic dependency in constructor

我正在编写 Java class 来管理包含 Cell 个对象的十六进制映射 (class GameMapImpl implements GameMap)。单元格对象保存在HashMap<Hex,Cell>中,其中键是十六进制图上的位置。

Cells 和 GameMap 之间的整体关系过去非常紧密,两者之间存在循环依赖关系,但我正在尝试重构它以允许更轻松的测试。

(以下代码已简化)

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
}

每个 Cell 都需要有一个 Hex position 字段,以便它可以在 GameMap 中查找自己的位置。此外,Cell 个对象需要引用所属 GameMap,以执行常见的有用操作,例如寻找它们的邻居。

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
    }
}

GameMap 内置于 GameMapBuilder 中,它为 GameMap 构造函数提供 Set<Cell>

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;
    }
}

正如您可能看到的那样,最后一段代码是错误的,因为我引用了一个不存在的 gameMap 变量。这就是我的问题:如果我在初始化单元格后创建 GameMap,我无法将它传递到 Cell 的构造函数中。如果我做相反的事情,我无法将 Set<Cells> 传递给 gameMap 以便正确初始化。

有没有更有经验的程序员知道如何正确解耦这两个 classes?

或者,我可以回到之前的紧耦合设计,假设只有当 GameMap 创建 Cell 时 Cell 才存在。作为这些小物件并包含在同一个包裹中,这没什么大不了的。

我认为这里的问题是您使用了生成器。基本上,您在构建器中所做的就是从 GameMap 构造函数中获取代码。如果要将此代码放在 GameMap 构造函数中,则可以将 this 赋给 Cell 构造函数。

如果您仍想将生成器代码与 GameMap 代码分开,您可以在 GameMap 构造函数调用的生成器上创建一个静态方法并传递 thisGameMap 对象)作为参数。

使用 setter 将 GameMap 传递给 Cell 而不是构造函数。您可以使其受包保护以将其隐藏在其他代码中,并在 GameMap 构造函数或 GameMapBuilder 中的另一个循环中调用 setter。所有已知的 DE 框架都使用 setters 来解决循环依赖。