Stop Using Constructors
What is the traditional approach to creating an object of a class? We provide constructor method(s) to a class to create instances of it. But there is another technique that should be followed by software engineers or developers. What is that? A class can provide a public static factory method that can return an instance of the class.
Consider static factory methods instead of constructors
This is all about this article. A class can provide its client with static factory methods with or without public constructors. But providing a public static factory method(s) instead of a public constructor(s) has both advantages and disadvantages.
Advantages of using static factory methods
- Static factory methods have names whereas constructors don’t.
You can give a static factory method a self-descriptive name which is easier to use and that makes client code easier to read.
// TypeScript or JavaScript
Array.from("foo");
// PHP
Closure::fromCallable("strlen");
// Java
Date date = Date.from(instant);
Here we didn’t need constructors to create objects. All classes have a static factory method with a name — from
. We can easily guess how objects are being created from other types; this [‘f’, ‘o’, ‘o’]
array is being created from string foo
, for example.
2. Unlike constructors, static factory methods are not required to create a new object each time they’re invoked.
Normally, when we create some objects of a class then each instance becomes discrete from others. What if we don’t allow a class to always create a new object? Using a constructor it is not possible to return the same object again and again but a static factory method.
// TypeScript/JavaScript
static singleton(): Database {
if (! Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
// PHP
static function singleton(): Database
{
if (! static::$instance) {
static::$instance = new Database();
}
return static::$instance;
}
// Java
static Database singleton() {
if (null == instance) {
instance = new Database();
}
return instance;
}
Now we have a guarantee that no two instances exist through a life-cycle of a script and the class will be a full immutable class. But using a constructor you cannot do that. Otherwise, returning the same instance from repeated invocations can improve performance if the object is an expensive one to create.
3. Unlike constructors, static factory methods can return an object of any subtype of their return type.
This means if a method’s return type is a base/parent class or an interface then that method can make it return an object of a child class of that parent class or interface. It is not possible by a constructor now.
// TypeScript/JavaScript
static newInstance(target: String): Parent {
if ('specified type' === target)
return new Child();
else
return new OtherChild();
}
// PHP
static function newInstance(string $target): Parent
{
if ('specified type' === $target)
return new Child();
else
return new OtherChild();
}
// Java
static Parent newInstance(String target) {
if ('specified type' == target)
return new Child();
else
return new OtherChild();
}
Notice the return type of the method. The method is returning a child object instead of a parent. Because that parent class may be an abstract class or interface. So this is a great flexibility to choose the class of the returned object using a static factory.
4. The class of the returned object can vary from call to call as a function of the input parameters.
This can be understood clearly by looking at the Java OpenJDK implementation of this EnumSet.of()
method which uses this EnumSet.noneOf()
method to make it work. Note that I have omitted parts of these two methods’ signatures to make them as simple as possible.
static EnumSet of(E e1, E e2, E e3) {
EnumSet result = noneOf(...); // see below
result.add(e1);
result.add(e2);
result.add(e3);
return result;
}
static EnumSet noneOf(...) {
...
if (universe.length <= 64)
return new RegularEnumSet(...); // note this line and
else
return new JumboEnumSet(...); // this line
}
Which implementation of EnumSet
will be returned depends on the length of the arguments of EnumSet.of()
method. If the length is less than or equal to 64 RegularEnumSet
object will be returned otherwise, JumboEnumSet
.
5. The class of the returned object need not exist when the class containing the method is written.
This advantage is about the service provider’s service access API. This access API may be implemented by a static factory method. The intent of this advantage is that you get the default implementation of a service in the absence of the criteria you search that service for.
// Expect a specific implementation
Service.get('get me this service', 'with this implementation');
// Without specifying a particular implemenation
Service.get('get me this service');
Note the second example code. The criterion is missing here. So in the absence of a particular criterion it should return an object of the default implementation of the expected service.
Disadvantages of using static factory methods
- Classes without public or protected constructors cannot be subclassed.
If you provide static factory method(s), generally, you make the constructor of that class private. That means the class has no public or protected constructor(s). Therefore, you cannot inherit from the class. This is true for some programming languages.
But this may not be a problem. Because there is a principle — Favor composition over inheritance.
2. The second limitation of static factory methods is that they are hard for programmers to find.
If you provide static factory methods to a class, then the users of that class may not know what the names are. So they need documentation for the class. But now-a-days this is not a problem anymore because IDEs help a lot.
I strongly suggest that you follow community standards and best practices. By following them, you help yourself first, then others.
Now we can tackle the second limitation by following common naming conventions as follows:
- from — A type-conversion method that takes a single parameter and returns a corresponding instance of this type. Take string return array for example.
Array.from("foo");
2. of — An aggregation method that takes multiple parameters and returns an instance of this type that incorporates them, for example.
Array.of(1, 2, 3); // [1, 2, 3]
Set<Rank> faceCards = EnumSet.of (JACK, QUEEN, KING);
3. valueOf — A more verbose method alternative to from
and of
.
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
4. instance or getInstance — Returns an instance that is described by its parameters (if any) but cannot be said to have the same value.
Registry::getInstance(); // Returns an registry object
StackWalker luke = StackWalker.getInstance(options);
5. create or newInstance — Like instance or getInstance, except that the method guarantees that each call returns a new instance.
ReflectionClass::newInstance($param1, $param2, ...);
Object newArray = Array.newInstance(classObject, arrayLen);
6. getType — Like getInstance, but used if the factory method is in a different class. Type is the type of object returned by the factory method.
// Here FileStore is the target type
FileStore fs = Files.getFileStore(path);
7. newType — Like newInstance, but used if the factory method is in a different class. Type is the type of object returned by the factory method.
// Here BufferedReader is the target type
BufferedReader br = Files.newBufferedReader(path);
8. type — A concise alternative to getType and newType.
// Here List is the target type
List<Complaint> litany = Collections.list(legacyLitany);
Decision
Now the question is: Should I always use static factory methods over constructors? It depends. Both static factory methods and constructors have their own uses. But you should first consider static factory methods before using constructors.
Note that this publication is a short version of “Item 1: Consider static factory methods instead of constructors” from the Effective Java book by Joshua Bloch. I strongly recommend the book. And you go through “Item 1”.