Skip to main content

Constructors and creation methods 🏗️

Most object-oriented programming languages have the concept of a constructor – a special function which creates a new instance of a class, often using a new keyword.

When constructors are not enough #

A construction crane lifting functions.

While constructors can be used to create objects, sometimes a constructor doesn’t communicate the programmer’s intention very effectively. This could be because instantiating the class in question isn’t as straight-forward as “creating a new object”, or because there are many alternative constructors. In those circumstances it might be useful to use well-named functions to create instances of the class instead.

Joshua Kerievsky introduced the term creation method for these kinds of methods. The term is broad enough to cover many types of functions, but they are often simple and direct in their nature. They are similar to, but often simpler than, the the GOF pattern factory method which focuses on encapsulates the creation of an object, hiding the instantiation logic from the user.

Classes that use creation methods often do so by exposing static methods on the same class (in Java), or through the class’ companion object (in e.g. Kotlin or Scala). In many cases, the regular constructor is made private to force the use of the creation methods.

A simple example could look as follows.

public class Name {  
    public static Name fromFull(String fullName) {  
        var parts = fullName.split(" ", 2);  
        if (parts.length < 2) throw new IllegalArgumentException();  
        return new Name(parts[0], parts[1]);  
    public Name(String first, String last) {  
        // ...  

You can then use it like this.

var name = Name.fromFull("John Doe");
// name.first = "John"
// name.last = "Doe"

So when are creation methods appropriate? How do they differ from constructors semantically? Let’s take a look at some different scenarios.

Are side-effects involved? #

A first reason to use a creation method is when the creation of an object requires or implies some kind of side effect. Constructors are typically assumed to be side-effect free. Most people would not expect a constructor to modify global data, and definitely not to make a network call or access a database.

So when a side-effect is needed to create the instance, I would create a function which reads the data and then creates the new object with the resulting data.

An example from the Java standard library, but which exists in most logging frameworks, is the typical “initialize logger for class” line.

var logger = new Logger("com.example.Thing") // ERROR: Private constructor
var logger = Logger.getLogger("com.example.Thing");

Under the hood, the call to getLogger may cause the logging configuration file to be loaded from disk. (It also caches Logger instances, but we will get to that later.)

Is computation required? #

I tend to use creation methods when the instance creation requires non-trivial computation. While not strictly necessary, it communicates to the reader that “this is not your ordinary field-setting constructors, something else is happening here”.

An example is Java’s Pattern class which represents a regular expression. When creating a Pattern the provided regular expression string is parsed and compiled into a form which allows fast execution.

var pattern = new Pattern("a[bc]*"); // ERROR: Private constructor  
var pattern = Pattern.compile("a[bc]*"); // OK

Possibly, a creation method which does a lot of work should be seen as a separate responsibility and moved to a a separate factory class. This ensures that a single class does not have more than one responsibility. On the other hand, it makes the code a bit more abstract.

Do you actually get a new object? #

A creation method may be used to hide that you don’t (always) get a new object instance. Perhaps we can reuse a previous instance, or there is only a few possible instances that can be created.

For example, the Logger.getLogger function mentioned before hides the fact that the logging framework caches previously created Logger instances to improve performance. Calling Logger.getLogger("com.example.Thing") twice will return the exact same object.

Another example would be the Java class Charset. There is a limited number of possible charsets that the system will understand. Therefore the class does not provide a public constructor, but rather exposes the creation method forName(String).

var charset = new Charset("ISO-8859-1"); // ERROR: Charset is abstract
var charset = Charset.forName("ISO-8859-1"); // OK

Do you get an object at all? #

In some cases, you can have a creator method to say that the thing you are trying to create may not be possible to create. A constructor can only communicate this by throwing an exception. In scenarios where this is an expected event, rather than an exceptional one, a creation method can help as it can return null or a special object representing the error.

In Java, the TimeZone.getTimeZone method used to return null if the time zone id you provide does not exist. However, in Java 8 the implementation was changed to return the GMT timezone instead. Both of these represent different takes on the idea of returning a special value in special cases.

var tz = TimeZone.getTimeZone("Invalid/TimeZone");
// tz will be null or a GMT TimeZone object, depending on Java version

This also provides an example of using creation functions as a “guard” to ensure no invalid object instances are created. In languages which support it, creation methods can return a Result or Either type to represent either a successful result or an error.

Hide the actual implementation #

Another common reason to use a creation method could be to hide the fact that it’s actually a (private) subclass which is being instantiated.

Some creation methods conditionally return different classes depending on the input. One example is the DriverManager.getConnection function which returns completely different implementations of the Connection interface depending on the database URL provided.

// Typically returns a org.postgresql.jdbc.PgConnection
var conn1 = DriverManager.getConnection("jdbc:postgresql://...");
// Typically returns a com.mysql.cj.jdbc.ConnectionImpl
var conn2 = DriverManager.getConnection("jdbc:mysql://...");

A different example is the ExecutorService interface where the Exectutors class provides multiple creation methods which return different types of executors.

var ex1 = Executors.newVirtualThreadPerTaskExecutor(); // ThreadPerTaskExecutor  
var ex2 = Executors.newFixedThreadPool(4); // ThreadPoolExecutor  
var ex3 = Executors.newWorkStealingPool(); // ForkJoinPool

In this example, the Executors class acts as a helpful facade providing a single place to create any type of executor as well as other executor-related things.

Provide descriptive names #

In other cases, the functionality could have been expressed using regular constructors, but using creation methods give you a chance to provide helpful names.

An example in Java is the EnumSet class, which provides a number of creation methods like noneOf and allOf. In the example below, both of these cases could have been implemented using constructors, but the name really helps to distinguish between their behavior.

var es1 = EnumSet.noneOf(MyEnum.class);
var es2 = EnumSet.allOf(MyEnum.class);

Do you have more ideas on the difference between creation methods and constructors?