重构:删除构造函数中的重复项

Refactoring: removal of duplication in constructor

我好像是咖啡不够让我看清下面的问题

假设我有一个 class,其中包含两个构造函数和多个字段。一个构造函数是无参数构造函数,一个字段依赖于另一个字段。另一个构造函数为其其中一个字段获取注入值。示例:

public class Practice {
    private final int n;
    private final char c;
    private final Map<String, String> m;
    private final Set<String> s;

    public Practice() {
        this.n = 0;
        this.c = 'a';
        this.m = new HashMap<>();
        this.s = m.keySet();
    }
    
    public Practice(Set<String> s) {
        this.n = 0;
        this.c = 'a';
        this.m = new HashMap<>();
        this.s = s;
    }
}

我的问题:如何消除两个构造函数之间的重复代码?

第一次失败尝试:

public Practice() {
    this(new HashMap<>(), new HashMap<>().keySet());
}

public Practice(Set<String> s) {
    this(new HashMap<>(), s);
}

private Practice(int n, char c, Map<String, String> m, Set<String> s) {
    this.n = 0;
    this.c = 'a';
    this.m = m;
    this.s = s;
}

当然,这会失败,因为无参数构造函数创建了两个单独的映射而不是一个映射。

对于初始版本,您可以从默认构造函数传递一个 null,然后在设置 s:

时检查 null
public class Practice {
    private final int n;
    private final char c;
    private final Map<String, String> m;
    private final Set<String> s;

    public Practice() {
        this(null);
    }
    
    public Practice(Set<String> s) {
        this.n = 0;
        this.c = 'a';
        this.m = new HashMap<>();
        this.s = null == s ? m.keySet() : s;
    }
}

同样可以更新3个构造函数的版本:

public Practice() {
    this(null);
}

public Practice(Set<String> s) {
    this(0, 'a', new HashMap<>(), s); // as all args constructor is private
}

private Practice(int n, char c, Map<String, String> m, Set<String> s) {
    this.n = n;
    this.c = c;
    this.m = m;
    this.s = null == s ? m.keySet() : s;
}

如果一个参数依赖于另一个参数,您可以通过添加额外的构造函数来解决问题。在这种情况下 private Practice(Map<String,String> map)

public Practice() {
    this(new HashMap<>());
}

public Practice(Set<String> s) {
    this(new HashMap<>(), s);
}

private Practice(Map<String,String> map) {
    this(map, map.keySet());
}

private Practice(Map<String,String> map, Set<String> s) {
    this.n = 0;
    this.c = 'a';
    this.m = map;
    this.s = s;
}

您可以在将变量定义为成员时对其进行初始化。在您的构造函数中,您可以坚持严格初始化那些在初始化后可以保存不同值的变量。

另请注意:由于 nc 是原语,因此它们是 static 的合适候选者,因为它们也被标记为 final。这不适用于 m.

public class Practice {
    private static final int n = 0;
    private static final char c = 'a';
    private final Map<String, String> m = new HashMap<>();
    private final Set<String> s;

    public Practice() {
        this.s = m.keySet();
    }
    
    public Practice(Set<String> s) {
        this.s = s;
    }
}

最后 - 没有必要删除所有重复的代码。尽管我通常遵循 rule-of-three 或当重复的块很大时,这方面的规则各不相同。