The idea: control how objects are made

Most objects are made with new and a constructor. Two classic creational patterns change that. A Builder lets you put a complicated object together one part at a time and only creates it when every part is ready. A Singleton makes sure a class has only one object, and hands that same object to everyone who asks.

Left: client code calls uri, header and timeout on an HttpRequest.Builder, each returning this; build() checks the parts and creates one immutable HttpRequest. Right: OrderService and EmailService both call AppConfig.getInstance() and get arrows to the same AppConfig@1 object
A builder makes many objects, each complete; a singleton makes one object that everybody shares.

Builder: one part at a time

Java's HttpRequest has a uri, a method, headers, a timeout and a body. Most are optional. Instead of one constructor with all of them, you call HttpRequest.newBuilder() and then one method per part: uri(…), header(…), timeout(…), POST(…). Each call stores one field on the builder object. No request exists yet. (Demo: build a request step by step)

Every setter ends with return this, so the calls can be written as one chain: HttpRequest.newBuilder().uri(u).timeout(t).build(). That is called a fluent interface. The page writes each call on its own line so you can follow it.

build() checks once, then creates

build() is the one place where the parts are checked together. A request without a uri makes no sense, so Java's build() throws IllegalStateException and creates nothing. You never get a half-made object that fails later. The builder keeps its fields: set the uri and call build() again. (Demo: build() refuses a missing uri)

When the check passes, build() calls the product's constructor once, with the whole builder. The new HttpRequest copies the values into final fields and has no setters, so it is immutable. Immutable objects can be shared between threads and cached without defensive copies. The builder can be reused to make HttpRequest@2.

Why not just a constructor?

Without a builder, every combination of optional parts needs its own constructor, or one constructor with many positional arguments and null for the ones you skip. That is the telescoping constructor. The call site no longer says what each value means, and two arguments of the same type can be swapped without any error.

Two ways to make the same request. Top, a constructor call new HttpRequest with five positional arguments including null and 10, marked unclear. Bottom, the builder chain with named calls uri, header, timeout and build, marked clear
Positional arguments hide what each value means; builder calls name every part.
Way to createOptional partsChecksResult
Telescoping constructorone overload per combination, or nullin the constructorcan be immutable, hard to read
Empty constructor + setterscall the setters you neednone: the object exists half setmutable
Buildercall the builder methods you needonce, in build()immutable and complete

Singleton: one instance for everyone

AppConfig has a private constructor, so no other class can write new AppConfig(). It keeps its one object in a static instance field. getInstance() creates the object on the first call (lazy initialization) and returns the same one on every later call. On the page, new AppConfig() runs once however many services ask. (Demo: one shared instance)

The pitfall: hidden global state

A singleton is a global variable with a class around it. In Demo: hidden global state, EmailService gets the config and expects the default timeout of 30 s. Then OrderService calls setTimeout(5 s) for its own needs. Next time EmailService reads the timeout it gets 5 s. It never changed anything, and nothing in its constructor says it depends on AppConfig: the call AppConfig.getInstance() is buried inside its methods.

The same thing breaks tests. One test changes the singleton, the next test inherits the change, and the result depends on the order the tests run in. You also cannot hand a test a fake config, because the code fetches the real one itself.

Thread safety: the lazy-init race

The lazy getInstance() is a check-then-act: test instance == null, then create. If two threads run it at the same moment, both can see null before either assigns the field. Both run new AppConfig(), the second overwrites instance, and the two services now hold different objects. (Demo: race, lazy init without a lock)

Time runs down. Without a lock: T1 sees instance null, T2 sees instance null, T1 creates AppConfig@1, T2 creates AppConfig@2 and overwrites instance, two objects. With synchronized: T1 takes the lock and creates AppConfig@1, T2 waits, then sees instance is set and returns AppConfig@1, one object
Without a lock both threads pass the null check; with synchronized the second thread waits and finds the object.

Making getInstance() synchronized fixes it: the second thread waits for the lock, then sees the object is there. (Demo: race fixed with synchronized) The cost is a lock on every call. Common alternatives: create the object eagerly in a static final field, the holder class idiom (the JVM initializes a nested class once, thread-safely, on first use), a one-element enum, or double-checked locking with a volatile field.

Using one instance without a Singleton

Often you want one object without the global access. Create it once at startup and pass it into the constructors of the classes that need it: dependency injection. Spring beans are single instances by default in exactly this way. The dependency is now visible in the constructor, and a test can pass in its own config. A config object that is immutable, like the builder's product, removes the hidden-state problem too.

See also Strategy and Factory Method Patterns, another way to keep new in one place, SOLID Principles for dependency inversion, and Decorator, Proxy and Adapter, whose virtual proxy also creates an object lazily.

What the page leaves out

The Gang of Four Builder also has a Director that runs a fixed sequence of builder calls, and builders for different representations of the same steps. Builders generated by tools (Lombok's @Builder), step builders that force a call order with types, and Kotlin or Python named arguments, which remove much of the need, are left out. For Singleton, the page leaves out serialization and reflection creating a second object, one instance per class loader, and memory visibility without volatile.

On the mind map

Open the Software Design mind mapSoftware DesignPrinciples: keep each change localPrinciplesDesign patterns: named fixes for common designsDesign patternsArchitecture: the shape of a whole appArchitectureEnterprise app patterns: logic, SQL, locks, money (PoEAA)Enterprise app patternsGit storage: content named by its hashGit storageGit branches & merging: pointers to commits, then mergeGit branches & mergingTesting: check code in isolationTestingStrategy and FactoryStrategy and Factorypick behaviour by name, swap at runtimeState patternState patternstate objects replace a big switchCommand and undoCommand and undoeach action is an object with undo()Decorator, Proxy, AdapterDecorator, Proxy, Adapterwrappers that share one interfaceObserver vs pub-subObserver vs pub-subobserver calls directly; pub-sub via brokerBuilder and SingletonBuild step by step vs one shared instanceComposite and VisitorComposite and VisitorTreat a tree uniformly; add ops outside itChain of responsibilityChain of responsibilityEach handler acts or passes the request onOpen the Software Design mind mapSoftware Designnamed fixes for common designsDesign patternsStrategy and FactoryStrategy and Factorypick behaviour by name, swap at runtimeState patternState patternstate objects replace a big switchCommand and undoCommand and undoeach action is an object with undo()Decorator, Proxy, AdapterDecorator, Proxy, Adapterwrappers that share one interfaceObserver vs pub-subObserver vs pub-subobserver calls directly; pub-sub via brokerBuilder and SingletonBuild step by step vs one shared instanceComposite and VisitorComposite and VisitorTreat a tree uniformly; add ops outside itChain of responsibilityChain of responsibilityEach handler acts or passes the request on
This page on the Software Design mind map · 28 pages in 7 branches · open it to see the whole map and test yourself in recall mode.