Skip to main content

Command Palette

Search for a command to run...

Object construction in Java

Some design patterns to consider

Published
6 min read
Object construction in Java
K

I am a freelance agile software engineer from Belgium.

Java is an object-oriented programming language. At some point developers will need object creation. Either because they want to pass through data, store data or transform data: in Java you need to construct your objects. Here are some ways that I've seen it happen with my thoughts on them.

(static) Factory methods

Let's have a look at the following example.

@Setter // lombok annotation for shorter code
public class Book {
  private String title;
  private String authorName;
  private String isbn;

  private Book() {}

  public static Book create(String title, String authorName, String isbn) {
    final var book = new Book(); // Java 9 var FTW
    book.setTitle(title);
    book.setAuthorName(authorName);
    book.setIsbn(isbn);
    return book;
  }
}

The Book class has a static method which you can call from anywhere. I've seen such Factory Methods on the POJO class itself or on utility Factory classes. There's not much difference, except maybe the visibility of the constructor.

If your static Factory method is not in the POJO class, chances are high that your setters are public as well. This means you expose the internals of your class and how to set them. That's a shame.

When the static Factory method is part of the class, you can improve your code by making your constructor and setters private. But what is the difference with a plain old constructor?

Constructors

"A constructor is an entry point to a new object. It accepts some arguments, does something with them, and prepares the object to perform his duties." - Yegor Bugayenko in Elegant Objects Vol1.

public class Book {
  private String title;
  private String authorName;
  private String isbn;

  public Book(String title, String authorName, String isbn) {
    this.title = title;
    this.authorName = authorName;
    this.isbn = isbn;
  }
}

In this example you can see:

  • there's no particular need for setters. If you want setters you can have them, but you don't need them to construct your object.
  • The Book class knows how to fill in its properties at construction.

Unless you're using setters everywhere, which is a bad practice in my opinion, you're going to write a constructor anyway. Why not use it for what it was intended: constructing your object. Why write extra code for something baked into the programming language? For me, this is the default way to construct objects in Java.

The Builder pattern

At this point we need to discuss the Builder pattern. This pattern, noted in the GoF book as a creational pattern, allows you to construct a complex object through a so-called Builder. The Lombok project helps in keeping your builders concise. Here's an example.

@Builder
public class Book {
  private String title;
  private String authorName;
  private String isbn;
}

public class BuilderExample {
  public static void main(String[] args) {
    final var funnyBook = Book.builder()
      .title("I can't make this up: life lessens")
      .authorName("Kevin Hart")
      .isbn("1501155563")
      .build();

     doSomethingWith(funnyBook);
  }
}

What I dislike about this pattern is the ability to not fill in properties. While this may not be intended, you can create a Book without an isbn or without a title. While you may want this for certain objects, there's also the opportunity of overloading your constructors so that you support this use case. Let's have a look at a different Book class.

public class Book {
  private String title;
  private String authorName;
  private String isbn;
  private String isbn13;

  public Book(String title, String authorName, String isbn) {
    this(title, authorName, isbn, null);
  }

  public Book(String title, String authorName, String isbn, String isbn13) {
    this.title = title;
    this.authorName = authorName;
    this.isbn = isbn;
    this.isbn13 = isbn13;
  }
}

You can see in this example that constructors allow you to have optional fields as well. But what is most important: it is impossible to construct an object while forgetting the required fields, because they are required by all constructors.

"Actually, the more constructors you have, the better - and the more convenient your classes are for me, their user." - Yegor Bugayenko in Elegant Objects Vol1.

I'm not preaching for you to stop using the Builder pattern. I have used it myself plenty of times, when the use case is the following: we want to construct an object fast and we don't care about (most of) its properties. I mostly see this use case in tests. It's not uncommon to open a project of mine and find the following Builder implementation (with Lombok annotations, but not the Builder annotation).

@Setter
@Accessors(fluent = true)
@NoArgsConstructor(access = PRIVATE)
public class BookBuilder {
  private String title;
  private String authorName;
  private String isbn;
  private String isbn13;

  public static BookBuilder book() {
    return new BookBuilder();
  }

  public Book build() {
    return new Book(title, authorName, isbn, isbn13);
  }
}

public class BuilderExampleTest {
  @Test
  void aJUnit5Test() {
    final var book = book().authorName("Stephen King").build();
    assertThat(book).isNotNull(); // assertj
  }
}

Note that I firmly believe that objects should know how to create themselves properly and that it is a bad practice to allow an object to be created with null values. For testing purposes, we can allow such stubs if the properties are not needed for the given test. Another note: this is mostly the case if you have objects with many fields. That itself is bad practice and indicates that you are having problems modelling objects.

Factory beans

This post started with static Factory methods. I already expressed my dislikes for the pattern, but there is a close relative that I am a fan of. The Factory pattern is different: it tells us to create a Factory class with a method that creates the POJO we want constructed. Imagine a BookFactory that creates Books, but looks up the isbn13 first.

@RequiredArgsConstructor // lombok
public class BookFactory {
  // Can get autowired by DI framework
  private final IsbnRepository isbnRepo;

  public Book create(String title,
                                  String authorName,
                                  String isbn) {
    final var isbn13 = isbnRepo.findIsbn13For(isbn);
    return new Book(
      title,
      authorName,
      isbn,
      isbn13
    );
  }
}

The BookFactory still uses the constructor to create the actual instance. Its main purpose is data retrieval. It is concerned with knowing which repository to call or which transformation to do to create a complex object. So if you have a constructor that does very complex things, your class might have multiple responsibilities. I suggest a Factory for that case.

Conclusion

Although they were the start of this post, I am not a fan of (static) Factory methods. You can do the same with plain old constructors. Builders and Factories do have their use.

Builders are easy to create stubs in tests, but I would never use them in production code... Unless you are creating some sort of library for which it makes sense. Keep it simple, and use common sense.

Factory beans are useful for data retrieval or complex parameter transformations. Note that a Factory bean should never replace a constructor. If your Factory is using setters to fill all the properties of your object, you've missed the point.

An object should know how it should be created. Why else do we call the programming language "object-oriented"?

A

Builders usually used to built immutable objects. Like you mentioned it is possible to use constructors for this purposes. But builders come handy when your immutable object contains a bunch of optional fields. In case of constructors you will end up with telescoping constructors. Exposing an all args constructor is a bad practice if you have quite a bunch of fields (usually more than 6), it looks bad and bulky. This is where builder pattern come to rescue.

Problem of inconsistent object is not a problem of the builder usage, it is a probem of the object itself. You will anyway end up applying some checks if state of your object depends on the args values. Even the required fields can be passed as null that is equivalent to not setting them at all. Builder has nothing to do with that. If some invalid parameter passed, an IllegalArgumentException should be thrown. You can make it either in construction, or in a build method of builder.

If you want to guarantee that some mandatory args will be always indicated by caller, you can make builder constructor to accept them. I usually do private constructor for builder and add static factory method for builder itself:

public static class Builder {

  public static Builder of(Integer arg1, String arg2) {
    return new Builder(arg1, arg2);
  }

  private Integer arg1;
  private String arg2;
  private Foo arg3;
  private Bar arg4;

  private Builder(Integer arg1, String arg2) {

    this.arg1 = arg1;
    this.arg2 = arg2;
  }

  // other builder methods
}
K

Thanks for your thoughts, Artem Abeleshev!

I agree, although I think that objects with a lot of optional fields may indicate a conceptual problem in the codebase. Maybe the object needs to be split up. Sometimes you'll see that some optional fields are always filled in together. Maybe that can be reworked to a grouped concept of those fields. In the end, it all depends on the context of the application. :-)

What I like about constructors with many parameters is that it points out that there is a code smell. It screams to be refactored. A builder might hide that.

I think your solution for mandatory fields, together with builders, is a good one. Great call!