Possibly one of the most interesting topics in Computer Science, which I personally believe is best learned as needed rather than upfront since finding a realistic use case is essential. I’ll be using Refactoring Guru for reference.

Factory

Singleton

A very simple pattern that is said to violate the single responsibility principle (at least, when misused). It enforces exactly one instance of that singleton which serves as a global access point—you can’t just new a second one due to a private constructor.

A solid choice when accessing any central resource that is unique across the application, like a configuration manager, logging system, or database. Be wary of multi-threaded environments, however.

public class DatabaseConnection {
    // 1. Static field to hold single instance
    private static DatabaseConnection instance;
 
    // 2. Private constructor
    private DatabaseConnection() {
        System.out.println("Initializing DB connection...");
    }
 
    // 3. Public method to get the instance
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection(); // lazy initialization
        }
        return instance;
    }
 
    public void query(String sql) {
        System.out.println("Executing query: " + sql);
    }
}

Builder

One of the more fun/easier ones that most people have come across. Let’s say you want to construct an object that has a lot of fields, some of which may be optional. You could make a new constructor for each likely combination of fields and you could use null values to ignore an optional parameter.

Won’t that look lovely? E.g. new House(4, 2, 4, true, null, null, null, true, new Resident(...), ...)

Alternatively, we can move all of that logic out into a nested Builder subclass, which requires the following:

  1. A stepwise construction - Constructed incrementally, rather than one shot.
  2. An intermediate builder object - The nested class that handles and holds the partial construction state.
  3. Deferred creation - The final object isn’t usable until it’s done being built.
  4. Internal mutable state - Not to be confused with functional composition (e.g. stacking .map, .filter) which returns a complete object after every call, a Builder accumulates configuration.

All of that to say it looks something like this:

OkHttpClient client = new OkHttpClient.Builder() // Use the nested builder class
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .retryOnConnectionFailure(true)
    .build(); // Complete construction

Realistically, you can get away with calling a lot of things a “builder pattern” since the definition has become muddled with avoiding giant constructors. So while it’s not correct, you may see pipelines and declarative descriptions referred to as a builder.

const result = [1, 2, 3, 4, 5] // Pipeline example
  .filter(x => x % 2 === 0)
  .map(x => x * 10)
  .reduce((sum, x) => sum + x, 0);
 
const query = { // Declarative description example
  table: "users",
  where: { active: true },
  orderBy: "created_at",
  limit: 10
};