Item - 1 Effective Java Consider Static Factory Methods instead of Constructors

Berat Yesbek
4 min readAug 8, 2023

--

This writing will discuss item 1 in Effective Java, recommended by Joshua Bloch.

The standard way to create an instance of a class is to provide a public constructor, which is invoked when creating a new instance. However, there is an alternative method for obtaining an instance of a class that can sometimes be more flexible than using a public constructor. In the following discussion, we’ll explore both ways, detailing the advantages and disadvantages of each approach step by step.

Public Constructor

When a class has a public constructor, the constructor is accessible from any class within the code. The constructor is the first method invoked when a client creates a new class instance.

Public constructors have several advantages, including simplicity and discoverability. They provide a straightforward way to instantiate an object, making it easier for developers to understand and work with the code.

However, there are also some disadvantages to using public constructors. They cannot return a value other than an instance of the object, don’t have a distinct name that identifies different construction processes and can be subject to overloading restrictions. Furthermore, having a constructor with many parameters can lead to complexity, making the code harder to read and maintain. [1] [2]

DatabaseConnection.java



public class DatabaseConnection {

private final String connectionString;
private Integer poolSize = 8;

public DatabaseConnection(String connectionString) {
this.connectionString = connectionString;
}

public DatabaseConnection(String connectionString, Integer poolSize) {
this.connectionString = connectionString;
this.poolSize = poolSize;
}

public void connect() {
System.out.println("---> connected successfully! \n connection-string: " + connectionString + "\n poolSize: " + poolSize);

}

}

Test.java

public class Test {
public static void main(String[] args) {
DatabaseConnection databaseConnection = new DatabaseConnection("jdbc:postgresql://localhost:5432/test");
databaseConnection.connect();

DatabaseConnection databaseConnectionWithPool = new DatabaseConnection("jdbc:postgresql://localhost:5432/test", 10);
databaseConnectionWithPool.connect();
}
}

The purpose of the code is to create a new instance of the DatabaseConnection class, which has two constructors. One constructor accepts only a connection string and uses a default pool size, while the other constructor allows both a connection string and a custom pool size to be specified. By providing these two constructors, the class offers flexibility in establishing a connection by using default settings or allowing customization as needed.

As you can see, using public constructors offers Simplicity, Consistency, and Discoverability. The code base becomes easy to understand when utilizing public constructors, as they make the process of creating new instances quite simple and straightforward. Developers are generally familiar with using constructors to instantiate objects, and this approach is common practice in the programming world.

Static Factory Methods

Static factory methods provide more flexible designs instead of public constructors.

DatabaseConnection.java

import java.util.Objects;

public class DatabaseConnection {

private final String connectionString;
private Integer poolSize = 8;

private static DatabaseConnection databaseConnection = null;

private DatabaseConnection(String connectionString) {
this.connectionString = connectionString;
}

private DatabaseConnection(String connectionString, Integer poolSize) {
this.connectionString = connectionString;
this.poolSize = poolSize;
}

public void connect() {
System.out.println("---> connected successfully! \n connection-string: " + connectionString + "\n poolSize: " + poolSize);
}

/**
* This method provide singleton instance
* @param connectionString
* @return
*/
public static DatabaseConnection getInstance(String connectionString) {
if (Objects.isNull(databaseConnection)) {
databaseConnection = new DatabaseConnection(connectionString);
}
return databaseConnection;
}

public static DatabaseConnection getInstanceWithPoolSize(String connectionString, Integer poolSize) {
return new DatabaseConnection(connectionString, poolSize);
}

}

Test.java

public class Test {
public static void main(String[] args) {
DatabaseConnection databaseConnection = DatabaseConnection.getInstance("jdbc:postgresql://localhost:5432/test");
databaseConnection.connect();

DatabaseConnection databaseConnectionWithPool = DatabaseConnection.getInstanceWithPoolSize("jdbc:postgresql://localhost:5432/test", 10);
databaseConnectionWithPool.connect();

}
}

The purpose of this code is to create a new instance from the DatabaseConnection class that utilizes static factory methods. The getInstance() method allows creating a single instance using a connection string and the default pool size. The getInstanceWithPoolSize() method facilitates the creation of an instance with both a connection string and a specified pool size. These factory methods provide a structured way to create instances with different configurations depending on the requirements.

As you can see, static factory methods provide readability, flexibility, clarity, control over creation, descriptive method name, and potential reuse.

Encapsulating logic within a static method allows us greater control over how and when objects are created. This opens up opportunities for implementing design patterns such as singleton, adding validations, and more. Unlike constructors, static factory methods have specific names, providing more information about what they do. This feature enhances clarity and readability, making the code more self-explanatory. This feature directly increases clarity and readability. Moreover, static factory methods provide potential reuse when we need an existing connection or subtype we can return it.

Advantages of Static Factory Methods that mentioned by Joshua Bloch in Effective JAVA.

1. Static factory methods have names, unlike constructors. [2]

increase readability and clarity.

2. Static factory methods are not required to create a new instance whenever invoked. [2]

This feature provides singleton. And it can significantly improve performance. Avoiding unnecessary duplicate objects.

3. They can return an object of any subtype of their return type. [2]

This feature gives us flexibility and reusability

4. Static factories are where the class of the returned object can vary from call to call as a function of the input parameters. [2]

This feature provides flexibility and encapsulation of logic

The main limitation of providing only static factory methods is that classes without public or protected constructors cannot be subclassed.

A second shortcoming of static factory methods is that they are hard for programmers to find.

Lastly, static factory methods might decrease and ruin the OOP design.

NOTE: Static factory methods are not the same as Factory Method pattern

Conclusion

Use static factory methods Judiciously. For specific requirements, Static Factory Methods can be highly beneficial. We’ve learned that they enhance flexibility, readability, clarity, reusability, and performance. However, they might also have downsides, potentially diminishing or undermining OOP design principles. It entirely depends on the requirements.

References

https://www.quora.com/What-are-the-disadvantages-of-Java-constructors [1]

Bloch, Joshua. Effective Java Programming Third Edition. [2]

Robert C. Martin, Clean Code. [3]

--

--

Berat Yesbek
Berat Yesbek

No responses yet