2

Suppose I have a (DTO style) class with a field that is a list:

public class MyClass {
    private List<String> stuff = new LinkedList<>();
}

There are a few choices to allow mutation of the field:

// provide only a setter that *replaces* the list with the contents of the parameter
public void setStuff(List<String> list) {
    stuff = new LinkedList<>(list); // safe copy, or clear() + addAll(), whatever
}

// provide only a getter, allowing direct mutation
public List<String> getStuff() {
    return stuff; // deliberately allowing getStuff().add("foo") etc
}

// provide delegating mutator methods for allowed mutations, eg
public void addStuff(String s) {
    stuff.add(s);
}
// and removeStuff(String), clearStuff(), etc as actual need arises

Assuming the only callers are (trusted) same-project code, which is the "best" approach?

1 Answer 1

3

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.

3
  • I thought so. Actually, I've already gone down the proxy road for add, but not for validation; rather to provide a consumer of a single value to hand to a stream. Previously, we were collecting to a list just so we could hand it to the DTO. btw, the DTO is mutable because it's passed through the hands of lots of enrichment objects, who each mutate the part they're responsible for. Finally, yes Jackson emits it as JSON. Could have used a builder, then created an immutable DTO, but there are just too many fields (inherited API that can't be changed)
    – Bohemian
    Commented Jun 3, 2015 at 6:33
  • @Bohemisn Yup, in your case pragmatism totally trumps any immutable elegance. What you are describing already is a kind of builder, though you skip building a final object and instead directly build the JSON.
    – amon
    Commented Jun 3, 2015 at 6:40
  • 1
    I never looked at it that way before - the DTO is really a builder for JSON (which it is)... I like it! I will keep that arrow in my quiver when the strict pattern nazis start asking questions.
    – Bohemian
    Commented Jun 3, 2015 at 6:43

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.