如何在双向关联中实例化不可变 类?
How to instantiate immutable classes in a bidirectional association?
我有两个不可变的 类:User 和 Department,它们使用双向关联连接 - 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不改变,但允许关联改变,你就避免了这种的问题。
在您的情况下,User
和 Department
对象将相互关联,并且每个对象都有一个 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 实例
我有两个不可变的 类:User 和 Department,它们使用双向关联连接 - 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不改变,但允许关联改变,你就避免了这种的问题。
在您的情况下,User
和 Department
对象将相互关联,并且每个对象都有一个 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 实例