The Object Instantiation Nightmare: Setters and Constructors

In previous posts, we talked about decoupling our objects using Dependency Injection and Inversion of Control.

However, there are some kinds of objects that is difficult to decouple using those techniques, like Entities, Value Objects and Data Transfer Objects.

Have you ever seen how your clients’ classes (like unit tests) break when you add a new attribute to a Value Object constructor?

Here, we are going to talk about an approach to solve this challenge, named the Object Instantiation Nightmare, using Setters and Constructors.

TRY IT YOURSELF: You can find the source code of this post here.

The Object Instantiation Nightmare Series

Why Don’t We Use Dependency Injection and Inversion of Control to Create Entities?

We talked about how Dependency Injection and Inversion of Control helps us to create objects and decoupled them, however, we have some kind of objects that you won’t create using those techniques. Entities, Value Objects and Data Transfer Objects.

Those objects usually are created at hand, and, they are usually complex to created, with a lot of attributes and logic. They are the core logic in any well designed application, much more, when you use Domain Driven Design.

NOTE: For the propose of this post, we are going to group Entities, Value Objects and Data Transfer Objects as a Business Objects.

Business Objects are Coupled to Our Application

Well, Business Objects are our core in any application, it means, a lot of components are coupled to them. So, let’t talk about it using the following example:

Person and PersonValidation dependencies

There, we can see the Person business object and a validation class named PersonValidation.

PersonValidation requires Person to validate it, so there is a dependency, soft, but it is there.

PersonValidationTest has our unit tests for PersonValidation. As you can see, there is a hard dependency. Same happens with Person and PersonTest. There is a hard dependency because your unit tests cannot exist without your test subject.

NOTE: Why we are checking unit tests here? well, your unit tests are part of your application, therefore, you need to code them well, decoupled them, and extend them.

PersonValidationTest depends on Person too, because, to validate the behavior of PersonValidation, you need to create and send a Person object.

As you can see, if you changed something important in the Person class, you could break at least three classes at the same time on compile time.

Okey, how do you decrease the coupling between Business Objects and your application? Let’s focus on how we create them.

Creating an Object using Setters

Well, the first approach to create an object is using its setters methods to fill it. For instance, let’s see the Person class.

class Person {
  private String name;
  private List<String> addresses;
  private LocalDate birthDate;

  public Integer calculateAgeFrom(
    LocalDate currentDate) {
    return Period.between(birthDate, currentDate)
      .getYears();
  }

........

  public void setAddresses(List<String> addresses) {
    this.addresses = addresses;
  }

  public LocalDate getBirthDate() {
    return birthDate;
  }

  public void setBirthDate(LocalDate birthDate) {
    this.birthDate = birthDate;
  }
}

See the calculateAgeFrom method? we are going to use this as an example of Person logic. This method only requires the birthDate from the Person class to work, it means, to create an unit test, we just need to set the birthDate.

Now, let’s create an unit test for the method calculateAgeFrom:

    @Test
    public void calculateAgeFrom_from1988_ageIs31() {
        Person person = new Person();

        person.setBirthDate(
                LocalDate.of(1988, 5, 5));

        Integer age = person.calculateAgeFrom(
                LocalDate.of(2019, 5, 5));

        assertThat(age).isEqualTo(31);
    }

Well, this is good, as a calculateAgeFrom only needs birthDate, we just created a Person and fill the birthDate.

Let’s create an external logic to validate some Person properties:

class PersonValidation {
  
  public boolean validate(Person person) {
    return person.getBirthDate() != null;
  }
  
}

Again, we created an unit test for this method:

   @Test
    public void validate_birthDayNotNull_true() {
        Person person = new Person();

        person.setBirthDate(
                LocalDate.of(1988, 5, 5));

        boolean valid = personValidation
                .validate(person);

        assertThat(valid).isTrue();
    }

This is good too, we just created the Person and set the data we need for the unit test.

TRY IT YOURSELF: You can find the source code of these unit tests here.

About Setters

Well, we can conclude some advantages:

  • You avoid creating multiple Person class constructors.
  • You can add new attributes to Person class, and the clients (like the unit tests) won’t break.

And some disadvantages:

  • Person class cannot be immutable. See here for more information about immutable objects.
  • You don’t know which Person attributes are required to build a consistent Person.
  • If you need to add more attributes to Person class, you will need to add setters too.
  • And remember, a set method is not a good name for your intent.

Creating an Object using Multiple Constructors

Well, the second approach to create an object is using multiple constructors. For instance, let’s see the following Person class.

class Person {
  private final String name;
  private final List<String> addresses;
  private final LocalDate birthDate;

  public Person(LocalDate birthDate) {
    this(null, Collections.emptyList(), birthDate);
  }

  public Person(String name) {
    this(name, Collections.emptyList(), null);
  }

  public Person(String name, List<String> addresses) {
    this(name, addresses, null);
  }

  public Person(String name, List<String> addresses,
                LocalDate birthDate) {
    this.name = name;
    this.addresses = Collections
            .unmodifiableList(addresses);
    this.birthDate = birthDate;
  }

  public Integer calculateAgeFrom(
          LocalDate currentDate) {
    return Period.between(birthDate, currentDate)
            .getYears();
  }

  ............
}

Here, we created multiple constructors to address multiple use cases, for instance, the line 6 shows the birthDate constructor. Moreover, our Personal class is immutable now.

NOTE: Is valid to create a Person only with its birthDate on line 3? well, that depends on your business rules, but, in my experience, that doesn’t make sense.

NOTE: We removed the setters to allow our Person class to be immutable.

Now, let’s create an unit test for the method calculateAgeFrom:

    @Test
    public void calculateAgeFrom_from1988_ageIs31() {
        Person person = new Person(LocalDate
                .of(1988, 5, 5));

        Person personCopy = new Person(LocalDate
                .of(1988, 5, 5));

        Integer age = person.calculateAgeFrom(
                LocalDate.of(2019, 5, 5));

        assertThat(age).isEqualTo(31);
        assertThat(person).isEqualTo(personCopy);
    }

There, we created a Person using our new constructor based on birthDate, and as Person class is immutable, we added statements to validate that feature.

TIP: You can read more about testing immutability here.

Let’s create an external logic to validate some Person properties:

class PersonValidation {
  
  public boolean validate(Person person) {
    return person.getBirthDate() != null;
  }
  
}

Again, we created an unit test for this method:

    @Test
    public void validate_birthDayNotNull_true() {
        Person person = new Person(
                LocalDate.of(1988, 5, 5));

        boolean valid = personValidation
                .validate(person);

        assertThat(valid).isTrue();
    }

Good, our unit test only creates the enough Person data to work.

TRY IT YOURSELF: You can find the source code of these unit tests here.

About Multiple Constructors

Well, we can conclude some advantages:

  • Person class is immutable now.
  • Person class doesn’t have setters methods anymore.
  • You can add new constructors based on your user cases to Person class.
  • You can add new attributes to Person class, and the clients (like the unit tests) won’t break, if you create specific constructors for those attributes.

And some disadvantages:

  • You don’t know which Person attributes are required if you have multiple ways to build the object.
  • Some Person constructors won’t make sense.
  • You will need to use a lot of constructors if you have multiple use cases.
  • You cannot have two constructors with the same parameters types but different intents.
  • If you need to add a new parameter to an existing constructor, you will break any client that uses that constructor.
  • If you have a class with a lot of attributes, and you need to build that object with all, the constructor will be ugly and long.

Final Thought

Well, as discussed setters and constructors and how to use them to decouple Business Object in our application.

However, both has advantages and disadvantages to take into account.

In my opinion, I prefer constructors instead of setters, they improve some important and critical staff that setters cannot handle, like immutability and consistency.

Now, there are other options like factory methods and builders, which we are going to discuss in the following posts.

Advertisements

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s