Builder and Singleton Patterns

OOP · lesson 16 of 43 · 4 min read

How to assemble complex immutable objects readably, and the one singleton form that is hard to get wrong.

Open this lesson in the learning hub

Key points

  • Builder: for objects with many optional fields. Named steps beat a constructor with seven arguments you must count.
  • Each step returns this so calls chain, and build() hands back one finished immutable object.
  • Put required values in the builder constructor and validate inside build(), so half-built objects never escape.
  • Singleton: exactly one instance. An enum with a single constant is the simplest correct version in Java.
  • That enum form is thread safe, lazy at class-load time, and immune to the reflection and serialization tricks.
  • Singletons are global state and make tests painful. Prefer one instance managed by your framework and injected.

Example

public class Main {
    // BUILDER: many optional settings, result is still immutable and readable.
    static final class HttpConfig {
        private final String url;
        private final int timeoutMs;
        private final boolean retry;

        private HttpConfig(Builder b) {
            this.url = b.url;
            this.timeoutMs = b.timeoutMs;
            this.retry = b.retry;
        }

        static Builder builder(String url) { return new Builder(url); }

        @Override public String toString() {
            return "HttpConfig[url=" + url + ", timeoutMs=" + timeoutMs + ", retry=" + retry + "]";
        }

        static final class Builder {
            private final String url;            // required
            private int timeoutMs = 2000;        // sensible defaults
            private boolean retry = false;

            private Builder(String url) { this.url = url; }

            Builder timeoutMs(int ms) { this.timeoutMs = ms; return this; }
            Builder retry(boolean on) { this.retry = on;    return this; }

            HttpConfig build() { return new HttpConfig(this); }
        }
    }

    // SINGLETON: an enum is the simplest thread-safe, serialization-safe version.
    enum Counter {
        INSTANCE;
        private int hits;
        int hit() { return ++hits; }
    }

    public static void main(String[] args) {
        System.out.println(HttpConfig.builder("https://api.dev").build());
        System.out.println(HttpConfig.builder("https://api.dev")
                .timeoutMs(500)
                .retry(true)
                .build());

        Counter.INSTANCE.hit();
        Counter.INSTANCE.hit();
        System.out.println("hits = " + Counter.INSTANCE.hit());
        System.out.println("one instance? " + (Counter.INSTANCE == Counter.valueOf("INSTANCE")));
    }
}

Build immutable objects step by step, and keep singletons injected rather than global.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.