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"?