Assume a Person has a Phone and a Phone has Contacts. Assume in the real world the Contacts of a Phone are sorted by their first name.
When I phone.add(Contact c)
, the phone sorts its contacts. Then this contact has its first name changed. How can phone know about it and sort its contacts?
public class Phone {
List<Contact> contacts = new ArrayList<>();
public Phone() {
}
public void addContact(Contact c) {
contacts.add(c);
Collections.sort(contacts);
}
public void delContact(Contact c) {
contacts.remove(c);
}
public List<Contact> getContacts() {
return Collections.unmodifiableList(contacts);
}
private static class Contact implements Comparable<Contact> {
String firstName, lastName;
public Contact(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Override
public int compareTo(Contact o) {
return firstName.compareTo(o.firstName);
}
@Override
public String toString() {
return "Contact [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
public static void main(String[] args) {
Phone phone = new Phone();
Contact george = new Contact("George", "Bush");
phone.addContact(george);
Contact bill = new Contact("Bill", "Gates");
phone.addContact(bill);
// They are sorted here
System.out.println(phone.getContacts());
bill.setFirstName("William");
// They are unsorted here
System.out.println(phone.getContacts());
}
}
How can I solve it in terms of domain? In my head I have the following options".
A Contact is immutable:
And does not offer a setFirstName
method. If the client wants to change the name of a contact, it must do:
phone.delContact(bill);
Contact william = new Contact("William", "Gates");
phone.addContact(william);
Now the problem is solved. But in the real world, a contact can have its first name changed and (say) replaced by a nickname or so. Plus, it is a pain if a contact has 20 fields.
The client asks Phone to sort:
bill.setFirstName("William");
phone.sortContacts();
Yes, ok, simple in terms of implementation. But it does not reflect the real world (?). The fact contacts are sorted in a Phone is a domain rule hence a domain's responsibility. So, should it be client's responsibility?
A Contact knows the phone:
In this case, Contact and Phone have a bi-directional association and when contact.setFirstName
is called, the contact calls the (package) private phone.sortContacts()
method. However, Phone and Contact are close. Plus, should a Contact know its phone? I mean in the real world? What if a Contact is added on multiple phones? Won't I end up with complex associations?
Phone observes its contacts:
Contact is now observable and when setFirstName
method is called, observers are notified. So, phone is an observer to all of its contacts. Downsides? Besides having to remove (and hold somewhere) the observers when delContact
is called?
A mediator:
A new domain class ContactAdder that depends on Phone, adds the contact to the phone and then calls the sortContacts method. Here, one more class to co-ordinate and maintain which has only too few responsibilities. Just one two line method.
To my eyes, the observer is a good solution and it remains a domain concern. However, if a bi-directional association is already there (say these are also say ORM persistence [JPA?] models - OneToMay/ManyToOne) the "contact calls directly phone.sortContacts" is a good solution too?
My question is, is there any solution I did not think of? Keep in mind that this is a more encyclopedic question. I am just looking the more "makes-sense" solution while at the same time wondering whats the typical approach to this kind of problem.
Also note that this cannot happen in my domain in one place. A phone could also have a Collection that follows the same idea in domain semantics.