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

时间:2015-04-08 11:07:37

标签: java immutability software-design

我有两个不可变的类:用户部门,它们使用双向关联连接 - 用户引用部门部门有一个用户列表。如何使用提供的用户创建新的部门实例?

代码:

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;
    }
}

6 个答案:

答案 0 :(得分:5)

我觉得你可以稍微修改你的设计并使用特殊的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);
    }    
}

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

一种方法是略微改变您理解 immutable 的含义。在面向对象的设计中,通常区分对象的属性及其关联。关联对象是对象具有引用的不同实体。如果放宽 immutable 的定义以表示对象的属性不会更改,但允许更改关联,则可以避免此类问题。

在您的情况下,UserDepartment个对象会彼此关联,每个对象都会有name属性。

答案 3 :(得分:1)

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

  • 用户对象只是字符串和部门之间的关联
  • 如果没有部门参考,用户参考就不存在。

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

注意:关于让this从构造函数中逃脱的@andremoniy said不应该成为习惯,但在这种情况下它是安全的,因为它只被传递给用户实例&#39;构造函数,在部门构造函数返回之前无法访问该用户实例。

这就是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的新实例的约束。考虑其他抽象或创建模式(如果您需要在保持不变性的同时支持从部门添加/删除用户,那么所有构造函数都是私有的,这是一个完全成熟的Builder实现)。

答案 4 :(得分:1)

我认为这也是建模的问题。可以认为用户有一个部门,一个部门有用户,但问题是你能从多大程度上查看用户和部门的数据?

除非您在概念上访问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(部门没有员工不需要),当您构建一个部门时,同样有效,当您需要拥有的是来自部门员工的PersonInfo时。 / p>

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

答案 5 :(得分:0)

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

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实例