4

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.

4 Answers 4

5

Contacts should only be sorted upon retrieving them. The call to getContacts() should sort contacts and then return the sorted list. The sorted collection could be an independent object from the original list of contacts, allowing you to make it unmodifiable.

Since a list of contacts is typically small (less than 1,000 items) there is no performance benefit to pre-sorting the contacts. Any difference in performance is likely impercievable by the end user.

As an alternative, you could create a class that specializes in displaying contacts, which does the sorting. This removes the need to sort the contacts in your domain object, and pushes this logic into the presentation layer of the application where it belongs.

7
  • 1
    Hopefully one day I won't take the bus to go to the bathroom.
    – George Z.
    Commented May 20, 2021 at 13:07
  • Exactly what I thought immediately when I read the question - a phone does not need to store contacts sorted, getting the contacts in sorted order is just a kind of view on the list of contacts.
    – Doc Brown
    Commented May 20, 2021 at 13:39
  • @GeorgeZ. I am puzzled and intrigued by your comment
    – FoxDeploy
    Commented May 20, 2021 at 14:21
  • 1
    @FoxDeploy I over-complicated things in my mind. That's all. My comment was a metaphor. Also, just for the record, having (getting) the "Contacts" sorted from a "Phone" is a domain term in my real-app domain. Phone/Contact was just an example to justify the need of sorting.
    – George Z.
    Commented May 20, 2021 at 14:24
  • @GeorgeZ to generalize this perspective ask yourself: which knows better how to sort? The phone or the view that presents the contacts? Maybe someday there’s a second view that wants to sort by last name. I don’t even want to know what that logic would look like in phone. Commented Jun 25, 2021 at 22:13
1

Not quite language agnostic, as it relies on functions being first-class values, but you could provide a modify operation.

As a sketch

public void modifyContact(Id id, Function<Contact, Contact> modifier) {
    Contact old = find(id);
    removeContact(old);
    Contact new = modifier(old);
    addContact(new);
}

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

    phone.modifyContact("Bill", old => old with { FirstName = "William" });

    // Still sorted here
    System.out.println(phone.getContacts());
}
0

Also, if performance is critical, I think this is a perfect example where you can try to use some self-balancing data structure. Re-sorting array on each insert/presentation might be ok when there is a small number of items, but the average complexity of doing so is O (n log n). But if you use something like a black-red tree, which is a self-balancing data structure - search, insert, and delete operations are all O(log n) which is n-times faster. (Where n is the number of items.)

I think Java has two default data structures implemented on top of black-red trees TreeMap and TreeSet. I guess you just need to implement a custom comparator to compare Contact objects in lexicographical order.

0

Swift or Objective C: A contact sends a notification out when it is changed. Anyone interested listens to the notification. For example phone objects. Every phone object listening checks if one of its contacts was changed and sorts its contacts and then sends a notification that the phone number has changed.

This requires that a contact is always the same object, on the other hand a contact can be changed without any knowledge of users of the contact. So when “Bill Gates” is changed to “William Gates”, any use of the contact can be updated automatically, including uses that you haven’t even thought about yet.

PS. Deleting a contact would send a different notification. Phone objects surely need to know about this as well. Creating a contact too, in case you have a list of all contacts, or even just display the number of contacts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.