Java – Nested POJOs, exposing collections, detecting changes

design-patternsencapsulationjava

I have a couple plain object classes to represent data read from a file. They look something like this:

public class Command {
    private String name;
    private List<Message> messages;
}
public class Message {
    private Type type;
    private Map<String, Object> params;
}

with getters and setters for the variables. However, I wasn't sure how to expose the messages list or the params map. At first I wanted to have a getter that just returns the list or map, to avoid having to write a bunch of add/remove/iterate methods for each list or map. The collection objects have these methods and many more, so it seemed most convenient to just return those objects. However, I also the objects to know when data is changed, so that a view can be updated accordingly, or to keep track of if a particular file has been modified and needs to be saved. This means I would have to implement delegates for add/remove/iterate/keyset/entrySet/etc for those fields, but would add a lot of extra methods and make the code much less convenient. What is the best practice here, when dealing with plain objects that can have lists, maps, sets, etc in them, while also being able to know when data is changed?

I also had a related question regarding whether a child object should have a reference to the object that created it. I was thinking about this because in the event that a Message object is modified, how does its parent Command object know about that change? Should the Message class have events that Command subscribes to with listeners, or should the Message object have a reference to the Command object it is contained in, and call a method/set a field in that Command object when data is modified? What is the best practice here, or should I completely change how file data is being stored as objects?

Best Answer

It is good practice not to expose contained collections directly to clients. There is a list of "standard" methods that you typically provide on the containing class to manage the relationship (get all, add, remove, etc.).

In theory, it's advised that you don't ever return the actual collection to the client, rather an immutable copy of it containing the same objects. This way the client can't modify the collection directly, but has to go through your delegate methods.

Bi-directional relationships are not a problem, but slightly more tricky; use them where needed, but avoid the additional complexity if you don't. You have to make sure that the bi-directionality is maintained correctly in the model, and not rely on client code to do it. The complexity will depend on which mutations are possible after construction, but generally:

  • If you add a Message to a Command, that Command must always be the one set on the Message (and vice versa)
  • If you remove a Message from a Command, the Command reference of the Message must be set to null (and vice versa)
Related Topic