How to instantiate immutable classes in a bidirectional association?
Asked Answered
G

6

7

I have two immutable classes: User and Department, they are connected using a bidirectional association - User has a reference to Department and Department has a list of Users. How to create a new Department instance with the provided Users?

Code:

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;
    }
}
Graph answered 8/4, 2015 at 11:7 Comment(8)
If you want really immutable Department you should use something like users = Collections.unmodifiableList(users). But you will lost ability to add users after that and you task becomes impossible :)Abstractionist
@Abstractionist that won't make the class immutable, since the caller still has a reference to the mutable list wrapped into an unmodifiable list.Borszcz
You are actually asking questions about design principles. But consider this: Should the department class really be immutable? Isn't it better suited as a mutable class that could add and/or remove users? Once this question is answered with yes, think about the best way to implement that. Should the constructor really take a list? Or should the class itself provide an add and a remove method. In the moment you designed a department as having a list that contains users. But you should design a department as having users. This is a difference!Saltus
@JBNizet Really? And how can caller archive this reference?Abstractionist
@Abstractionist This is a fundamental design question. In fact, it is wrong to have a constructor accepting a list argument!Saltus
List<User> mutable = new ArrayList<>(); Department d = new Department(mutable); mutable.add(new User());Borszcz
@JBNizet arh, you mean that. OkAbstractionist
Ooops! I was wrong with my first comment. OP is copying the content, not simply copying the reference! Shame on me ...Saltus
N
1

You can produce immutable Departments and Users with an extra constructor on Department. From the questions' code, it is inferred that

  • A User object is just an association between a String and a Department
  • User references can't exist without a Department reference.

Since Users are truly just Strings associated to a Department, a Department can be constructed with a List<String> that represents all User names to be included and use that List<String> to create a List<User> within the Department constructor.

Note: what @andremoniy said about letting this escape from a constructor should not be made a habit of, but it is safe in this case since it is only being passed to a User instance's constructor where that User instance can't be accessed before the Department constructor returns.

Here's what it would look like, in 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;
    }
}

One issue this solution has is that once a Department instance is created, it can be added to new instances of User without the constraint that a new instance of Department be created with an updated List. Consider other abstractions or creational patterns (a full blown Builder implementation where all constructors are private would be a good match here) if you need to support the addition/deletion of users from a Department while maintaining immutability.

Nickels answered 8/4, 2015 at 14:0 Comment(0)
A
5

I feel in you case you can slightly modify your design and use special UsersBuilder, i.e.

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

In general, it is not really good idea to use object's reference before its constructor finishes; but in this particular case it looks safe.

In this case these objects will be really immutable.

Abstractionist answered 8/4, 2015 at 11:24 Comment(0)
B
1

Instantiate Department with empty list of users. Then use the Department to instantiate User and add the user instance to the Department's users list.

Berky answered 8/4, 2015 at 11:10 Comment(1)
In this case Department's objects are still mutable :-(Abstractionist
A
1

One approach is to slightly alter what you understand immutable to mean. In object oriented design it is conventional to distinguish between the attributes of an object and its associations. Associated objects are different entities to which the object has references. If you relax the definition of immutable to mean that the attributes of the object do not change, but allow the associations to change, you avoid this kind of problem.

In your case, User and Department objects would be associated with each other, and each would have a name attribute.

Auer answered 8/4, 2015 at 12:36 Comment(0)
N
1

You can produce immutable Departments and Users with an extra constructor on Department. From the questions' code, it is inferred that

  • A User object is just an association between a String and a Department
  • User references can't exist without a Department reference.

Since Users are truly just Strings associated to a Department, a Department can be constructed with a List<String> that represents all User names to be included and use that List<String> to create a List<User> within the Department constructor.

Note: what @andremoniy said about letting this escape from a constructor should not be made a habit of, but it is safe in this case since it is only being passed to a User instance's constructor where that User instance can't be accessed before the Department constructor returns.

Here's what it would look like, in 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;
    }
}

One issue this solution has is that once a Department instance is created, it can be added to new instances of User without the constraint that a new instance of Department be created with an updated List. Consider other abstractions or creational patterns (a full blown Builder implementation where all constructors are private would be a good match here) if you need to support the addition/deletion of users from a Department while maintaining immutability.

Nickels answered 8/4, 2015 at 14:0 Comment(0)
A
1

I think this is a matter of modeling as well. This is ok to think that an User has a Department and a Department have Users, but the question is how deep can you look into data from User and Department ends?

Does it make sense unless conceptually you to access user.department.user[2].name? What about department.user[10].addresses[1].street?

I really don't think so on most scenarios. It's a matter of information domain. You have bondaries while accessing data and this can also be expressed somehow into your models.

If Object Modeling kind represents the real world, this is ok to think that when you go to a department, you will see dozens of people working there and most likely all you will be able to know about them is the counting and the their names perhaps. So what slices of data you should be able to see from your object?

My approach for this is:

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();

    // ...
}

I don't think i'd need to show how the builders would work since if you noticed, for this scenario, the bidirectional nature of relationship is never there. So when you build a Person, all you need is the DepartmentInfo (department no employees not required), and the same is valid when you build a Department, when all you need to have is the PersonInfo from department's employees.

That's my way to think this problem conceptually. Any comments?

Amphibole answered 22/12, 2015 at 21:35 Comment(2)
thanks for the answer, I have an added an answer with another solution, similar to your idea, what do you think about it?Graph
That's pretty much it, otherwise i don't think it's a real good thing to have sort of an entity called DepartmentWithUsers, but it surely works well. My way to tackle this is the same, but i prefered to grab the basic information into an *Info entityAmphibole
G
1

My solution is to: split one of the immutable classes into two classes: a class with the attributes and a class with the bidirectional association:

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

So to create a new user and a department instance you have to:

  • create a new Department instance
  • create a new User instance and pass the created Department instance
  • create a new DepartmentWithUsers instance and pass the created User instance
Graph answered 22/12, 2015 at 22:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.