首页 > 解决方案 > 构造函数中的复杂初始化

问题描述

我有一种模型可以管理不同数据的地图。我读过构造函数不应该包含业务逻辑,但我也读到构造函数可以自由地做他们需要做的事情来初始化对象的状态。如果给定构造函数中的映射 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;
    }
}

标签: javaoop

解决方案


我读过构造函数不应该包含业务逻辑,但我也读到构造函数可以自由地做他们需要做的事情来初始化对象的状态。

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

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


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

现在,您大概有一个业务领域对象类,称为类似 的东西User,它代表一个应用程序用户;因此,您可能很想将此业务规则实现为User类的不变量,这意味着实际上不可能有一个User违反此业务规则的实例。为此,您在构造函数中实现此规则(可能通过委托某种validateUsername方法或其他东西)。

这样做的一个问题是,让用户的用户名违反此规则并非不可能。如果一个重要的客户打电话来要求用户名сор,那么有人可能需要进入数据库并手动创建该用户 - 在这种情况下,您不希望User类在您第一次尝试加载该用户时爆炸数据库。

另一个问题是,随着时间的推移,规则变得越来越复杂,您最终可能会将相关数据提取到数据库或配置存储中,并且您显然不希望仅User在单元测试中实例化就需要数据库连接.

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


不幸的是,我对你的MapManager类的理解不够好,无法对其发表评论(实际上我有点怀疑它作为一个有凝聚力的类是否有意义),但如果它有帮助,这里有一些初始化逻辑例子被认为是“业务逻辑”:

  • 如果该类表示一个数据结构,例如双向链表java.util.LinkedList(它有一个构造函数,它接受一个现有的集合并复制它的内容(就像这些例子一样)。这不是“业务逻辑”,因为业务客户对它们没有意见;他们可能甚至不知道“链表”和“树”这两个术语。这些课程的一切都属于“技术”而非“商业”的范畴。java.util.HashMapjava.util.TreeMapjava.util.TreeSet
  • 如果该类有一个不应该为 null 的不可变字段,那么绝对期望它的构造函数会检查这一点并java.lang.NullPointerException使用信息性消息引发 a。在这里,业务规则可能会对技术实现产生一些影响——我们可以想象一个新的业务需求相当直接地转换为“这个字段现在必须可以为空”(尽管实际上这可能会以不同的方式实现,例如通过将字段更改为 a java.util.Optional) — 但这是一个技术规则,因为可能存在依赖它的下游代码,例如编写诸如this.username.toLowerCase()不检查 null 之类的东西。(这是一个实用主义的问题;如果不可能允许空用户名,为什么还要编写额外的代码来支持空用户名?最好只是预先验证。)

推荐阅读