Java 构造函数中复杂的初始化

Java 构造函数中复杂的初始化,java,oop,Java,Oop,我有一种模型,可以管理各种数据的地图。我读过构造函数不应该包含业务逻辑,但我也读过构造函数可以自由地执行初始化对象状态所需的操作。如果给定构造函数中的映射A和映射B,我想合并这两个映射并在第三个字段中设置结果,该怎么办。也许我也想做些清洁之类的。这是坏习惯吗?若然,原因为何 public class MapManager { private Map<String, Object> mapA; private Map<String, Object> map

我有一种模型,可以管理各种数据的地图。我读过构造函数不应该包含业务逻辑,但我也读过构造函数可以自由地执行初始化对象状态所需的操作。如果给定构造函数中的映射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;
    }
}
公共类映射管理器{
私人地图;
私有地图地图;
私有地图组合地图;
公共地图管理器(地图地图A、地图地图B){
this.mapA=mapA;
this.mapB=mapB;
this.combinedMaps=initCombinedMap(mapA,mapB);
}
公共地图getMapA(){
返回mapA;
}
公共地图getMapB(){
返回mapB;
}
公共映射getCombinedMap(){
返回组合图;
}
私有静态映射initCombinedMap(映射mapA、映射mapB){
Map combinedMap=新HashMap(mapA);
如果(mapB!=null){
forEach(combinedMap::putIfAbsent);
}
返回组合图;
}
}

我认为这很好,但取决于您使用的是什么,它可能有点过于复杂。例如,如果只有在构造函数中才组合
Map A
Map B
,则应删除
initCombinedMap
方法,并在实际构造函数中进行组合

您还可以使用
javafx.util
包中的
Pair
为地图创建一个容器。然后,您的getter可以从对中检索
mapA
键和
mapB

以下是您的班级在实施这些建议后的看法:

public 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;
    }
}
我读过构造函数不应该包含业务逻辑,但我也读过构造函数可以自由地执行初始化对象状态所需的操作

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

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


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

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

这其中的一个问题是,用户的用户名违反了这一规则并不是不可能的;如果一个重要的客户打电话要求用户名,那么可能有人需要进入数据库并手动创建该用户-在这种情况下,您不希望第一次尝试从数据库加载该用户时
user
类崩溃

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

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


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

  • 如果该类表示一个数据结构,例如双链接列表(
    java.util.LinkedList
    ),哈希表(
    java.util.HashMap
    ),红黑树(
    java.util.TreeMap
    java.util.TreeSet
    ),当然,它的构造函数应该包含初始化数据结构的逻辑——特别是如果它有一个接受现有集合并复制其内容的构造函数(这些例子都是这样做的)。这不是“业务逻辑”,因为业务客户对他们没有意见;他们可能甚至不知道“链表”和“树”这两个术语。这些课程的所有内容都属于“技术”类,而不是“商业”类
  • 如果该类有一个不应为null的不可变字段,那么它的构造函数将检查该字段,并用一条信息性消息引发
    java.lang.NullPointerException
    。在这里,业务规则可能会对技术实现产生一些影响——我们可以想象一个新的业务需求相当直接地转化为“这个字段现在必须为空”(尽管事实上,它可能会在
    public class MapManager<K, V> {
    
        private Pair<Map<K, V>, Map<K, V>> maps;
        private Map<K, V> combinedMaps;
    
        public MapManager(Map<K, V> mapA, Map<K, V> mapB) {
    
            this.maps = new Pair<>(mapA, mapB);
            this.combinedMaps = new HashMap<>(mapA);
    
            if (mapB != null) {
                mapB.forEach(combinedMaps::putIfAbsent);
            }
        }
    
        public Map<K, V> getMapA() {
            return maps.getKey();
        }
    
        public Map<K, V> getMapB() {
            return maps.getValue();
        }
    
        public Map<K, V> getCombinedMap() {
            return combinedMaps;
        }
    }