Creational Patterns

Creational Design Patterns #

Creational patterns concern the process of object creation. They help make a system independent of how its objects are created, composed, and represented.

Singleton #

Motivation: Some classes must have exactly one instance (e.g., a file system, a database connection pool, a window manager).

Intent: Ensure a class has only one instance and provide a global point of access to it.

Singleton class diagram

Kotlin has built-in support for the Singleton pattern via the object keyword:

object DatabaseConnection {
    fun query(sql: String): List<String> {
        // ...
        return listOf()
    }
}

// Usage---only one instance ever exists
val result = DatabaseConnection.query("SELECT * FROM users")

A more traditional approach (useful in languages without object):

class Singleton {
    // keep the constructor private to prevent other classes from instantiating it
    private Singleton() { /* ... */ }

    private static Singleton instance = null;
    public static Singleton getInstance() {
        if (instance == null) { instance = new Singleton(); }
        return instance;
    }
}

Factory Method #

Motivation: We want to create an object whose concrete type may vary, but the code for constructing and using the object should not depend on that concrete type.

Intent: Define an interface for creating objects in a superclass, but let subclasses decide which class to instantiate.

Factory Method class diagram

The client calls Creator.operation(), which internally calls factoryMethod(). Because the client depends only on the abstract Creator and Product types, adding a new product variant (e.g., ConcreteCreatorC + ConcreteProductC) requires no changes to existing client code. New behavior is introduced by adding new product/creator classes, not by modifying existing ones.

// Products
interface Button { fun render() }
class WindowsButton : Button { fun render() { /* ... */ } }
class HtmlButton : Button { fun render() { /* ... */ } }

// Creators
abstract class Dialog {
    // factory method
    abstract fun createButton(): Button

    // operation
    fun render() {
        val button = createButton()
        button.render()
    }
}

class WindowsDialog : Dialog() {
    override fun createButton(): Button = WindowsButton()
}

class WebDialog : Dialog() {
    override fun createButton(): Button = HtmlButton()
}

Abstract Factory #

Motivation: We want to create several objects belonging to several related abstract types (e.g., UI elements following a consistent theme), without specifying their concrete classes.

Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

Abstract Factory class diagram

This scales up the Factory Method pattern: instead of one factory method per creator, the abstract factory bundles multiple factory methods so that a family of products is always created together. The client depends only on the AbstractFactory and the product interfaces, so swapping an entire product family (e.g., switching from Material Design to Cupertino UI) only requires changing which concrete factory is provided.

interface UIFactory {
    fun createButton(): Button
    fun createCheckbox(): Checkbox
}

class MaterialUIFactory : UIFactory {
    override fun createButton(): Button = MaterialButton()
    override fun createCheckbox(): Checkbox = MaterialCheckbox()
}

class CupertinoUIFactory : UIFactory {
    override fun createButton(): Button = CupertinoButton()
    override fun createCheckbox(): Checkbox = CupertinoCheckbox()
}

Builder #

Motivation: Constructing objects with many parameters or optional components leads to unwieldy constructors—multiple overloads, hard-to-read call sites, and difficult validation during construction.

Intent: Separate the construction of a complex object from its representation, allowing step-by-step construction.

Builder class diagram

An optional Director class can define the order of building steps, separating the construction algorithm from the builder itself. Each builder encapsulates how a particular representation is assembled, keeping the construction logic cohesive within a single class rather than scattered across constructors.

class HttpRequest private constructor(
    val url: String,
    val method: String,
    val headers: Map<String, String>,
    val body: String?,
) {
    class Builder(private val url: String) {
        private var method = "GET"
        private var headers = mutableMapOf<String, String>()
        private var body: String? = null

        fun method(m: String) = apply { this.method = m }
        fun header(k: String, v: String) = apply { headers[k] = v }
        fun body(b: String) = apply { this.body = b }
        fun build() = HttpRequest(url, method, headers, body)
    }
}

// Usage
val request = HttpRequest.Builder("https://api.example.com/users")
    .method("POST")
    .header("Content-Type", "application/json")
    .body("""{"name": "Alice"}""")
    .build()

Prototype #

Motivation: Constructing certain objects is complex or resource-intensive, and the client needs copies of an existing object rather than building from scratch.

Intent: Provide a way to clone existing objects without depending on their concrete classes.

Prototype class diagram

An optional PrototypeRegistry keeps a catalog of commonly used prototypes, so clients can look up and clone pre-configured objects by key.

In Kotlin, the idiomatic way to clone is to use a data class and its built-in copy() method (already implements the Prototype pattern), which returns a shallow copy with optional overrides for specific properties.

data class Shape(
    val type: String,
    val color: String,
    val x: Int,
    val y: Int,
)

// Usage: clone via copy() with optional changes
val original = Shape("circle", "red", 10, 20)
val cloned = original.copy(color = "blue")

In both Kotlin and Java, there is also a built-in Cloneable interface for the Prototype pattern. This is useful when you need to customize the cloning behavior (e.g., deep copy instead of shallow copy):

data class Canvas(val width: Int, val height: Int, val shapes: List<Shape>) : Cloneable {
    override fun clone(): Canvas {
        return Canvas(width, height, shapes.map { it.clone() })
    }
}

Object Pool #

Motivation: Creating and destroying certain objects (e.g., database connections, threads) is expensive, and the application needs many short-lived instances.

Intent: Manage a pool of reusable objects, lending them out on demand and reclaiming them when no longer in use, rather than creating and destroying them repeatedly.

Object Pool class diagram

The pool keeps a set of pre-initialized objects ready for use. When a client needs one, it borrows from the pool; when done, it returns the object instead of discarding it.

class ConnectionPool(private val maxSize: Int) {
    private val available = ArrayDeque<Connection>()
    private val inUse = mutableSetOf<Connection>()

    fun acquire(): Connection {
        val conn = if (available.isNotEmpty()) available.removeFirst()
                   else Connection()
        inUse.add(conn)
        return conn
    }

    fun release(conn: Connection) {
        inUse.remove(conn)
        conn.reset()
        available.addLast(conn)
    }
}