The decision is between offering your own (proxy) methods, or returning an internal object to be mutated by the caller. Unless performance is the single most important goal of your architecture, the Law of Demeter would strongly suggest that you should rather implement proxy methods:
More correct: a proxy method is the ideal place to do validation that would otherwise have to be implicit and spread throughout the whole codebase. By not leaking a reference to internal state, you preclude the possibility of accidentally making that state invalid. setStuff
and addStuff
get this right, but getStuff
doesn't.
Less coupled: the return- and argument types of a method become part of the interface of that method. Therefore these types should be as narrow and general as possible. You sensibly try doing this by exposing the List
interface, and not a specific class type.
In the context of DTOs – which are really just serialization containers – most of this discussion does not matter. There is no internal state to be protected, there is only a value. However, once constructed there is no reason to mutate a DTO. By this argument, an outgoing DTO only needs a constructor and serialization support, whereas an incoming DTO only needs serialization support and getter methods. Unless used by the serialization mechanism, this does not need mutators or setters, so that we can favour an immutable design.
In particular, we would have the equivalent of setStuff
in the constructor, and a getStuff
accessor. This accessor would typically enforce immutability, but since the lifetime of a DTO ends once the contained values have been read and the values are only read at one point for each DTO, it would be safe to freely use and mutate those values.