我有一种模型可以管理各种数据的地图。我读过构造函数不应包含业务逻辑,但我也读过构造函数可以自由地执行初始化对象状态所需的操作。如果给定构造函数中的地图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.TreeMap
或java.util.TreeSet
),则绝对可以预期构造函数将包含用于初始化数据结构的逻辑-尤其是当它具有一个采用现有集合并复制其内容的构造函数时(如所有示例所述)。这不是“业务逻辑”,因为业务客户对此没有意见。他们甚至可能不知道“链表”和“树”这两个术语。这些类的所有内容都属于“技术”类别,而不是“业务”类别。
如果该类具有不应该为null的不可变字段,则绝对可以预期其构造函数将对此进行检查并使用信息性消息引发java.lang.NullPointerException
。在这里,业务规则可能会对技术实施产生一定的影响-我们可以想象一个新的业务需求,它可以直接转换为“该字段现在必须为空”(尽管实际上可能以不同的方式实现,例如通过将字段更改为java.util.Optional
)-但这是一项技术规则,因为可能存在依赖于此的下游代码,例如编写this.username.toLowerCase()
之类的东西时不检查null。 (这是一个实用主义的问题;如果不可能使用空用户名,为什么还要编写额外的代码来支持空用户名呢?最好只是预先进行验证。)
关于java - 构造函数中的复杂初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56429438/