构造函数中的复杂初始化

时间:2019-06-03 14:30:52

标签: java oop

我有一种模型,可以管理各种数据的地图。我读过构造函数不应包含业务逻辑,但我也读过构造函数可以自由地执行初始化对象状态所需的操作。如果给定构造函数中的地图A和地图B,该如何合并这两个地图并将结果设置在第三个字段中呢?也许我也想做一些清理工作。这是不好的做法吗?如果是这样,为什么?

public class MapManager {

    private Map<String, Object> mapA;
    private Map<String, Object> mapB;
    private Map<String, Object> combinedMap;

    public MapManager(Map<String, Object> mapA, Map<String, Object> mapB) {
        this.mapA = mapA;
        this.mapB = mapB;
        this.combinedMaps = initCombinedMap(mapA, mapB);
    }

    public Map<String, Object> getMapA() {
        return mapA;
    }

    public Map<String, Object> getMapB() {
        return mapB;
    }

    public Map<String, Object> getCombinedMap() {
        return combinedMap;
    }

    private static Map<String, Object> initCombinedMap(Map<String, Object> mapA, Map<String, Object> mapB) {
        Map<String, Object> combinedMap = new HashMap<>(mapA);

        if (mapB != null) {
            mapB.forEach(combinedMap::putIfAbsent);
        }

        return combinedMap;
    }
}

2 个答案:

答案 0 :(得分:1)

我认为这很好,但是根据您使用它的情况,可能会有些复杂。例如,如果仅将Map AMap B组合在一起是在构造函数中,则应删除initCombinedMap方法并在实际的构造函数中进行组合。

您还可以使用Pair包中的javafx.util为地图添加一个容器。然后,您的获取者可以从对中获取Key的{​​{1}}和mapA的{​​{1}}。

这是实现这些建议后您的班级的样子:

Value

另一个建议是通过用通用类型mapBpublic class MapManager { private Pair<Map<String, Object>, Map<String, Object>> maps; private Map<String, Object> combinedMaps; public MapManager(Map<String, Object> mapA, Map<String, Object> mapB) { this.maps = new Pair<>(mapA, mapB); this.combinedMaps = new HashMap<>(mapA); if (mapB != null) { mapB.forEach(combinedMaps::putIfAbsent); } } public Map<String, Object> getMapA() { return maps.getKey(); } public Map<String, Object> getMapB() { return maps.getValue(); } public Map<String, Object> getCombinedMap() { return combinedMaps; } } 替换为StringObject来泛化您的班级。这不会完全简化您的类,但会提供模块化,因为映射现在可以在实例化新的K时保留用户指定的任何类型。

V

答案 1 :(得分:1)

  

我读过构造函数不应包含业务逻辑,但我也读过构造函数可以自由地执行初始化对象状态所需的操作。

这两种说法都是很好的建议-并不矛盾。

我认为您的问题实际上是关于什么才算是“业务逻辑”。总体思路是,“业务逻辑”实现“业务规则”,即软件(不是开发人员)的实际“业务客户端”要求或可能关心的行为。


例如,假设一条业务规则说用户名中允许使用西里尔字母,除非(1)用户名还包括拉丁字母,或者(2)用户名中的每个西里尔字母都是一个这很容易被误认为是拉丁文对应对象。 (此业务规则有助于防止欺骗:我无法通过创建一个名为“Веn”的帐户来模拟一个名为“ Ben”的帐户,也不能通过创建一个名为“Димa”的帐户来模拟一个名为“Дима”的帐户。)

现在,您大概有一个业务域对象类,称为User之类,它代表应用程序用户;因此您可能会想将此业务规则实现为User类的不变式,这意味着实际上没有违反该业务规则的User实例是不可能的。为此,您可以在构造函数中实施此规则(可能通过委派某种validateUsername方法或其他方法来实现)。

一个问题是,实际上没有一个用户名违反此规则的用户是不可能的。如果重要客户打电话要求提供用户名сор,则可能需要有人进入数据库并手动创建该用户-在这种情况下,您不希望User类破坏用户名。第一次尝试从数据库中加载该用户。

与此相关的另一个问题是,随着时间的流逝,规则变得越来越复杂,您可能最终将相关数据提取到数据库或配置存储中,并且显然不想仅仅为了实例化{{ 1}}进行单元测试。

因此,更好的方法是区分User类的技术限制-例如必须填充的字段,否则事情将会崩溃,必须突变的字段,否则事情将会崩溃—以及用户帐户的业务限制。班级本身应该严格执行前者,而不是后者。相反,您应该有一个单独的方法来验证用户,并在将用户添加到数据库之前调用该方法。


不幸的是,我对您的User类的理解不够好,无法对此进行评论(实际上,我有点怀疑它作为cohesive类是有意义的),但如果可以帮助您,以下是一些初始化逻辑的示例,这些示例不是被视为“业务逻辑”:

  • 如果该类表示数据结构,例如双向链表(MapManager),哈希表(java.util.LinkedList),红黑树(java.util.HashMap或{{ 1}}),那么绝对可以预期其构造函数将包含用于初始化数据结构的逻辑-特别是如果它的构造函数采用现有集合并复制其内容(如所有示例所示)。这不是“业务逻辑”,因为业务客户对此没有意见。他们甚至可能不知道“链表”和“树”这两个术语。关于这些类别的所有内容都属于“技术”类别,而不是“业务”类别。
  • 如果类具有不应该为null的不可变字段,则绝对可以预期其构造函数将对此进行检查并使用信息性消息引发java.util.TreeMap。在这里,业务规则可能会对技术实施产生一定的影响-我们可以想象一个新的业务需求,它可以直接转换为“该字段现在必须为空”(尽管实际上可能以不同的方式实现,例如通过将字段更改为java.util.TreeSet)-但这是一项技术规则,因为可能有下游代码依赖该字段,例如编写类似java.lang.NullPointerException之类的东西而无需检查null。 (这是一种实用主义的问题;如果不可能使用空用户名,为什么还要编写额外的代码来支持空用户名呢?最好只是预先进行验证。)