The Object Instantiation Nightmare: The Factory Method Pattern

In a previous post, we talked about how to build objects like entities and value objects, using setters and constructors. We saw that both strategies have advantages and disadvantages.

In this post, we are going to move forward and check how factory methods can help us to mitigate those disadvantages.

NOTE: Do not forget why we don’t use Dependency Injection to solve this. You can see here how Dependency Injection works and here why this objects should be handled different.

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

The Object Instantiation Nightmare Series

Creating an Object using the Factory Method Pattern

The factory method pattern is a creational pattern focusing on decoupling the object creation:

Factory method pattern

It focuses on the creation of one object and its sub types.

Now, let’s see an example of this using our Person class.

Person factory method

In this case, the Creator and the Product, are the same class. Moreover, those factory methods are static methods, so, you can access them directly.

NOTE: This use of factory methods is common. You avoid creating a lot of classes and interfaces that might not be worthy to solve this particular problem. For instance, the java.time package uses those a lot, like LocalDate.of methods.

Let’s See Some Code

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

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

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

  public static Person newPersonWithBirthDate(
          LocalDate birthDate) {
    return new Person(null, Collections.emptyList(),
            birthDate);
  }

  public static Person newPersonWithName(String name) {
    return new Person(name, Collections.emptyList(),
            null);
  }

  public static Person newPersonWithNameAndAddresses(
          String name, List<String> addresses) {
    return new Person(name, addresses, null);
  }

  public static Person newFullPerson(String name,
                                     List<String> addresses,
                                     LocalDate birthDate) {
    return new Person(name, addresses, birthDate);
  }

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

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

Now, we created a full private constructor in line 6. This will allow us to have control on how this class should be created.

Here, we created multiple factory methods to address multiple use cases, for instance, the line 14 shows the birthDate factory method. Moreover, our Personal class is immutable now.

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

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

  @Test
  public void calculateAgeFrom_from1988_ageIs31() {
    Person person = Person
            .newPersonWithBirthDate(LocalDate
                    .of(1988, 5, 5));
    Person personCopy = Person
            .newPersonWithBirthDate(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 factory method 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 = Person.newPersonWithBirthDate(
            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 Factory Method Pattern

Well, we can conclude some advantages:

  • Person class is immutable.
  • Person class doesn’t have setters methods.
  • You can add new factory methods 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 factory methods for those attributes.
  • You can name with intent your factory method.
  • You can change the internal implementation of a factory method. For instance, adding logic or returning a sub type of the Person.
  • You can have two factory methods with the same parameters types but different intents.

And some disadvantages:

  • You don’t know which Person attributes are required if you have multiple ways to build the object.
  • Some Person factory methods won’t make sense.
  • You will need to use a lot of factory methods if you have multiple use cases.
  • If you need to add a new parameter to an existing factory method, you will break any client that uses that method.
  • If you have a class with a lot of attributes, and you need to build that object with all, the factory method will be ugly and long.

Final Thought

Well, we discussed setters and constructors before, now factory methods, and how to use them to decouple Business Object in our application.

As we can see, factory methods improves the downsides of setters and constructors, however, it has disadvantages too.

In the following post, we will talk about the builder pattern and how this could be the best option to solve this nightmare.

If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

4 comments

Leave a comment