Fully encapsulated

Let’s speak about common domain objects and how they are usually implemented. The tendency here is to define a bunch of accessors to share object’s state and returning nulls as an indicator of empty value. In this post I will try to refactor one of this objects to fully encapsulated one!

I have in mind those kinds of objects that has some optional values. It may be a person’s name for instance. I took this one only for simplicity. In almost every project a name is defined as an entity that can include a first part and a last part, sometimes it’s only one part sometimes there are both. If we google exising implementations, we will find that the majority of them has two getters getFirstName() and getLastName()

interface Name {
  
  String getFirstName();

  String getLastName();
}

What’s happened here? This object just provides two accessors to his internal state that violates encapsulation and allows you to look inside this torn object and take one part of him outside.

Now try to refactor him and go the vole. Some time ago I was imbued by printers instead of getters idea but hesitated to try in use for a while. Let’s do something similar now.
I’m calling this “source and output” pattern. To implement it you need to write your object as a source with single generic method print and add output interface as a protocol for all possible source states:

interface Source {

  <T> T print(Source.Out<T> out);

  interface Out<T> {

    T print(String foo);

    T print(String foo, Integer bar);
  }
}

Now back to our Name:

interface Name {
  
  <T> T print(Name.Out<T> out);

  interface Out<T> {

    T printFirst(String first);

    T printFull(String first, String last);

    T printLast(String last);
  }
}

We don’t care about name state anymore. It’s not possible to access it directly, you can only request a name to print itself into any output of your choice.

Now we are able to implement all kinds of names and call them properly:

class FirstName implements Name {

    private final String name;

    FirstName(String name) {
        this.name = name;
    }

    @Override
    public <T> T print(Out<T> out) {
        return out.printFirst(name);
    }
}

class FullName implements Name {

    private final String first;
    private final String last;

    FullName(String first, String last) {
        this.first = first;
        this.last = last;
    }

    @Override
    public <T> T print(Out<T> out) {
        return out.printFull(first, last);
    }
}

And all outputs for every need:

class FormattedOut implements Name.Out<String> {

  @Override
  String printFirst(String first) {
    return first;
  }

  @Override
  String printFull(String first, String last) {
    return String.format("%s %s", first, last);
  }
  
  @Override
  String printLast(String last) {
    return last;
  }
}

class JsonOut implements Name.Out<JSONObject> {

    @Override
    public JSONObject printFirst(String first) {
        final JSONObject json = new JSONObject();
        json.put("first", first);
        return json;
    }

    @Override
    public JSONObject printFull(String first, String last) {
        final JSONObject json = new JSONObject();
        json.put("first", first);
        json.put("last", last);
        return json;
    }

    @Override
    public JSONObject printLast(String last) {
        final JSONObject json = new JSONObject();
        json.put("last", last);
        return json;
    }
}

We just split object creation and all possible object formats here, as a result we can easily make unit tests for more complex name objects (e.g. SqliteName or JsonName) and for complex outputs (e.g. XmlOut or BundleOut) separately.
Also this printers are able to connect to any source that you want! You can combine them in many variants and use to convert object from one type to another:

Xml xml = new JsonName(json).print(new XmlOut());
String formattedName = new SqliteName(database).print(new FormattedOut("%s %s"));

About unit testing by the way. You can face small problems here with traditional asserts-testing. We just can’t check that internal state equals to some value. But it’s not so bad. If you are using Hamcrest library you can make easy-to-write matchers for this object. It may be MatcherName.HasFirst, MatcherName.HasLast etc.:

@Test
public void firstNameTest() {
  MatcherAssert.assertThat(
    "Can't read first name",
    new JsonName("{\"first\": \"Jimmy\"}),
    new MatcherName.HasFirst("Jimmy")
  );
}

As for me an ability to write unit test for every part of code is very important, usually it says about good design implicitly.

Written on June 28, 2017