java - 构造函数中的复杂初始化

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

最佳答案

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


这两个陈述都是很好的建议-并不矛盾。

我认为您的问题实际上是关于什么才算是“业务逻辑”。通常的想法是“业务逻辑”实现“业务规则”,这意味着软件(不是开发人员)的实际“业务客户端”要求或可能关心的行为。



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

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

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

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

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



不幸的是,我对您的MapManager类不太了解,无法对其进行评论(实际上,我有点怀疑它作为cohesive类是有意义的),但如果有帮助,这里有一些例子不视为“业务逻辑”的初始化逻辑:


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

关于java - 构造函数中的复杂初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56429438/

相关文章:

java - Symfony FOSElastica ResponseException 与 Elasticsearch

java - 使用 Netty 进行管道流式传输

c++ - Main 函数与状态之间的通信

javascript OOP - 不是函数

java - 下面的代码违反了哪一条 SOLID 原则?

java - Resource 标签中不需要提到存档的名称吗?

java - 如何减少 Spring 内存占用

c++ - 如何实现具有循环引用的对象的深拷贝或克隆?

c++ - 更改 LinkedList 以接受对象而不是 Int?

java - Google Drive Api result.getStatus().isSuccess() 始终为真