打破构造函数

时间:2015-06-14 13:14:59

标签: java dependency-injection dependencies circular-dependency decoupling

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

整个事物过去在细胞和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,为Set<Cell>构造函数提供GameMap

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以便正确初始化。

有经验的程序员是否知道如何正确地解耦这两个类?

或者,我可以回到之前的紧耦合设计,并假设Cell只在GameMap创建它们时才存在。作为这些小物件并包含在同一个包装中,它不会是一件大事。

2 个答案:

答案 0 :(得分:0)

我认为这里的问题是你使用了一个建设者。基本上你在构建器中所做的就是从GameMap构造函数中取出代码。如果您要将此代码放在GameMap构造函数中,则可以将this提供给Cell构造函数。

如果您仍希望将构建器代码与GameMap代码分开,则可以在构建器上创建一个静态方法GameMap构造函数调用并传递this({{ 1}} object)作为参数。

答案 1 :(得分:0)

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