如何在双向关联中实例化不可变 类?

How to instantiate immutable classes in a bidirectional association?

我有两个不可变的 类:UserDepartment,它们使用双向关联连接 - User 引用了 Department 并且 Department 有一个 User 的列表秒。如何使用提供的 Users?

创建新的 Department 实例

代码:

class User {
    private final Department department;
    private final String name;

    public User(Department department, String name) {
        this.department = department;
        this.name = name;
    }
}

class Department {
    private final List<User> users;
    private final String name;

    public Department(List<User> users, String name) {
        this.users = new ArrayList<>(users);
        this.name = name;
    }
}

用空的用户列表实例化部门。然后使用Department实例化User,将用户实例添加到Department的用户列表中。

我觉得在你的情况下你可以稍微修改你的设计并使用特殊的 UsersBuilder,即

class Department {
    final List<User> users;
    final String name;

    public Department(String name) {
        this.users = UsersBuilder.buildUsers(this);
        this.name = name;
    }
}


class UsersBuilder {

    public static List<User> buildUsers(Department department) {
        List<User> usersList = new ArrayList<>();

        // add users to the list via department reference

        return Collections.unmodifiableList(usersList);
    }    
}

一般来说,在对象的构造函数完成之前使用对象的引用并不是一个好主意;但在这种特殊情况下它看起来很安全。

在这种情况下这些对象将真正不可变。

一种方法是稍微改变您对 immutable 的理解。在面向对象的设计中,通常会区分对象的 属性 及其 关联 。关联对象是对象引用的不同实体。如果放宽immutable的定义意味着对象的attributes不改变,但允许关联改变,你就避免了这种的问题。

在您的情况下,UserDepartment 对象将相互关联,并且每个对象都有一个 name 属性。

您可以使用 Department 上的额外构造函数生成不可变的 Departments 和 Users。从问题的代码中可以推断出

  • 用户对象只是字符串和部门之间的关联
  • 没有部门引用就不能存在用户引用。

由于用户实际上只是与部门相关联的字符串,因此可以使用代表要包含的所有用户名的 List<String> 构造部门,并使用该 List<String> 创建 List<User> 在 Department 构造函数中。

注意: 关于让 this 从构造函数逃逸的事情不应该成为习惯,但在这种情况下它是安全的,因为它只被传递给 User 实例的构造函数在 Department 构造函数 returns 之前无法访问该 User 实例的位置。

这是 Java 8 时的样子:

public final class User {
    private final Department department;
    private final String name;

    public User(Department department, String name) {
        this.department = department;
        this.name = name;
    }

    public Department getDepartment() {
        return department;
    }

    public String getName() {
        return name;
    }
}

public final class Department {
    private final List<User> users;
    private final String name;

    ///Reversed argument list to avoid collision after erasure
    public Department(String name, List<String> users) {
        this.users = Collections.unmodifiableList(users.stream()
                .map((s) -> new User(this,s)).collect(Collectors.toList()));
        this.name = name;
    }

    public Department(List<User> users, String name) {
        this.users = Collections.unmodifiableList(users);
        this.name = name;
    }

    public List<User> getUsers() {
        return users;
    }

    public String getName() {
        return name;
    }
}

此解决方案的一个问题是,一旦创建了 Department 实例,就可以将其添加到 User 的新实例中,而不受使用更新的 List 创建 Department 的新实例的限制。如果您需要支持某个部门的 addition/deletion 用户同时保持不变性,请考虑其他抽象或创建模式(一个完整的 Builder 实现,其中所有构造函数都是私有的,这将是一个很好的匹配)。

我认为这也是建模的问题。可以认为一个用户有一个部门,一个部门有用户,但问题是您可以从用户端和部门端查看多深的数据?

除非您从概念上访问 user.department.user[2].name,否则它有意义吗? department.user[10].addresses[1].street 呢?

在大多数情况下我真的不这么认为。这是一个信息域的问题。你在访问数据时有边界,这也可以以某种方式表达到你的模型中。

如果对象建模类型代表了现实世界,那么认为当您去一个部门时,您会看到几十个人在那里工作,很可能您所能了解的只是计数也许还有他们的名字。那么您应该能够从您的对象中看到哪些数据片段?

我的做法是:

interface PersonInfo {
     String name();
     String lastName();
     default fullName() { return name() + " " + lastName(); }

     static PersonInfoBuilder personInfo() { return new PersonInfoBuilder(); }

     static class PersonInfoBuilder {
         ...
     }
}

interface Person extends PersonInfo {
     DepartmentInfo department();
     Set<Address> addresses();
     //...
}

interface DepartmentInfo {
     String name();
     String building();

     // builder ...
}

interface Department extends DepartmentInfo {
    Set<PersonInfo> employees();

    // ...
}

我认为我不需要展示构建器的工作方式,因为如果您注意到,对于这种情况,关系的双向性质永远不会存在。所以当你建立一个Person的时候,你只需要DepartmentInfo(department no employees not required),当你建立一个Department时也是如此,当你只需要department的employees的PersonInfo。

这就是我从概念上思考这个问题的方式。有什么意见吗?

我的解决方案是:将一个不可变 classes 拆分为两个 classes:一个带有属性的 class 和一个带有双向关联的 class :

class Department {
    private final String name;

    public Department(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class User {
    private final Department department;
    private final String name;

    public User(Department department, String name) {
        this.department = department;
        this.name = name;
    }
}   

class DepartmentWithUsers {
    private final List<User> users;
    private final Department department;

    public DepartmentWithUsers(Department department, List<User> users) {
        this.department = department;
        this.users = new ArrayList<>(users);
    }
}

因此,要创建新用户和部门实例,您必须:

  • 创建一个新的部门实例
  • 创建一个新的User实例并传递创建的Department实例
  • 创建一个新的 DepartmentWithUsers 实例并传递创建的 User 实例