In object-oriented programming, constructors serve as the standard approach for creating instances of classes. However, static factory methods offer a compelling alternative, providing a more versatile and flexible mechanism for object instantiation.
While constructors are important for creating basic objects, static factory methods can make code clearer, easier to understand, and more efficient.
In this blog post, we go beyond the usual and look at how static factory methods work and how they can improve object creation practices. We'll talk about why using static factory methods can be better than using constructors alone.
Through practical examples and insightful explanations, you'll gain a comprehensive understanding of this often-misunderstood technique and its potential to transform your programming approach.
A Little Context
The traditional way for a class to allow a client to obtain an instance is to provide a public constructor. However, there is another technique that should be a part of every programmer’s toolkit — The static factory method.
A class can provide a public static factory method
, which is simply a static method that returns an instance of the class.
Here’s a simple example from Boolean (the boxed primitive class for boolean). The method translates a boolean primitive value into a Boolean object reference.
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
Note that a static factory method is not the same as the Factory Method Pattern from Design Patterns. The static factory method discussed here has no direct equivalent in Designs Patterns.
A class can provide its clients with static factory method instead of, or in addition to, public constructors. Providing a static factory method instead of a public constructor has both advantages and disadvantages.
Advantages
1. One advantage of static factory methods is that, unlike constructors, they have names.
2. A second advantage of static factory methods is that, unlike constructors, they are not required to create a new object each time they’re invoked.
The Boolean.valueOf(boolean)
illustrates this technique: it never creates an object.
It can greatly improve performance if equivalent objects are requested often, especially if they are expensive to create.
The ability of static factory methods to return the same object from repeated invocations allows classes to maintain strict control over what instances exist at any time. Classes that do this are said to be instance-controlled.
They are several reasons to write instance-controlled classes:
- They allow a class to guarantee that it is a singleton or non instanciable
- It allows an immutable value class to make the guarantee that no two equal instances exist: a.equals(b) if and only if a == b.
3. A third advantage of static factory methods is that, unlike constructors, they can return an object of any subtype of their return type. This can give you great flexibility in choosing the class of the returned object. One application of this flexibility is that an API can return objects without making their classes public.
Hiding implementation classes in this fashion leads to a very compact API. This technique lends itself to interface-based frameworks, where interfaces provide natural return types for static factory methods.
4. A fourth advantage of static factories is that the class of the returned object can vary from call to call as a function of the input parameters. Any subtype of the declared return type is permissible. The class of the returned object can also vary from release to release.
5. A fifth advantage of static factories is that the class of the returned object need not exist when the class containing the method is written.
Drawbacks
But using this technique can have some warnings, here are a few that I have encountered so far.
The main limitation of providing only static factory methods is that classes without public or protected constructors cannot be subclassed.
For example, it is impossible to subclass any of the convenience implementation classes in the Collection Framework. Arguably, this can be a blessing in disguise because it encourages programmers to use composition over inheritance, and is required for immutable types.
A second caution of static factory methods is that they are hard for programmers to find. You can reduce this problem by drawing attention to static factories in class or interface documentation and by adhering to common naming conventions.
Here are some common names (also known as Naming Convention) for static factory methods:
from
— a type conversion method that takes a single parameter and returns a corresponding instance of this type, for example :
Date date = Date.from(instant);
of
— An aggregation method that takes multiple parameters and returns an instance of this type that incorporates them, for example:
Set<Rank> faceCards = EnumSet.of(JACK,QUEEN,KING);
valueOf
— A more verbose alternative to from
and of
, for example:
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance
or getInstance
— Returns an instance that is described by its parameters (if any) but cannot be said to have the same value, for example:
Employee john = Employee.getInstance(options);
newInstance
— Like instance or getInstance, except that the method guarantees that each call returns a new instance, for example:
Object newArray = Array.newInstance(classObject, arrayLen);
Reasons to Prefer Static Factory Methods
Expressing Intent
The main reason for using SFM (Static Factory Methods) it’s because they clearly express intent, which is something constructors fail to achieve. Take an example of an Order
class, where we are using constructors:
@Getter
public class Order {
private final LocalDate orderDate;
public Order(Customer customer) {
orderDate = LocalDate.now();
}
public Order(ShoppingCart cart) {
orderDate = LocalDate.now();
}
}
Now instantiate this class with these two different constructors
//...
Order firstOrder = new Order(new Customer());
Order secondOrder = new Order(new ShoppingCart());
//...
It can be difficult to guess the intent of these objects creation because the constructor is not documenting itself when creating these new objects with different arguments.
Now use the SFM to recreate those 2 orders objects:
@Getter
@Builder
@ToString
public class Order {
private LocalDate orderDate;
private Customer customer;
private Set<String> products;
public static Order fromCustomer(Customer customer) {
return Order.builder()
.customer(customer)
.orderDate(LocalDate.now())
.build();
}
public static Order fromShoppingCart(ShoppingCart cart) {
return Order.builder()
.customer(cart.getCustomer())
.products(cart.getItems())
.orderDate(LocalDate.now())
.build();
}
}
@Getter
@Builder
public class ShoppingCart {
private Set<String> items;
private Customer customer;
}
In your main method, instantiate 2 orders with SFM
public static void main(String[] args) {
var customer = Customer.builder()
.fullName("Thomas Grant")
.build();
var shoppingCart = ShoppingCart.builder()
.customer(customer)
.items(Set.of("Pen","Book","Paper"))
.build();
var OrderOne = Order.fromCustomer(customer);
var OrderTwo = Order.fromShoppingCart(shoppingCart);
}
Flexibility
Another reason to prefer SFM it is flexibility, as a developer it can be really convenient to add more tricky behaviour or validations when creation a new instance of our classes.
Take example for validation, we can create a new class for validating our order instance creation
public class OrderValidator {
public boolean validate(Order order) {
if (ObjectUtils.isEmpty(order.getProducts())) return false;
return !order.getOrderDate().isAfter(LocalDate.now());
}
}
In the Order.java class, we can create a static method to validate the creation of our object
private static boolean isValid(Order order) {
var validator = new OrderValidator();
return validator.validate(order);
}
And use that for example to validate the creation of an object instance fromShoppingCart
public static Order fromShoppingCart(ShoppingCart cart) throws InvalidObjectException {
var order = Order.builder()
.customer(cart.getCustomer())
.products(cart.getItems())
.orderDate(LocalDate.now())
.build();
if (!isValid(order)) throw new InvalidObjectException("Invalid Order");
return order;
}
private static boolean isValid(Order order) {
var validator = new OrderValidator();
return validator.validate(order);
}
Now every time we will need to create an object using the method fromShoppingCart
it will be far easier to apply verification on the order object before returning it.
I hope you enjoyed reading this, and I'm curious to hear if this tutorial helped you. Please let me know your thoughts below in the comments.
Don't forget to subscribe to my newsletter to avoid missing my upcoming blog posts.
You can also find me here LinkedIn • Twitter • GitHub or Medium
Ending
In summary, static factory methods and public constructors both have their uses, and it pays to understand their relative merits. Often static factories are preferable, so avoid the reflex to provide public constructors without first considering static factories.
Personally depends on the projects, but usually, I go to factory methods when there is need to apply some logic when creating objects, but if it’s simple objects instantiation, I will prefer using constructors instead.