devnotes.

Singleton and Factory Patterns

Design patterns are proven, reusable solutions to common software design problems — a shared vocabulary developers use to communicate design intent quickly ('use a Factory here') instead of re-explaining the same structure from scratch every time. Creational patterns specifically deal with object creation mechanisms.

Singleton ensures a class has only one instance, globally accessible throughout the program:

class DatabaseConnection {
    private static instance = null

    private function __construct() { /* expensive setup */ }

    static function getInstance() {
        if DatabaseConnection.instance == null:
            DatabaseConnection.instance = new DatabaseConnection()
        return DatabaseConnection.instance
    }
}

db1 = DatabaseConnection.getInstance()
db2 = DatabaseConnection.getInstance()
// db1 and db2 are the exact same object

The private constructor prevents new DatabaseConnection() from outside; getInstance() is the only way in, and it always returns the same object. Use with caution: Singleton is one of the most overused and criticized patterns — it introduces hidden global state, makes unit testing harder (you can't easily substitute a fake instance), and creates implicit coupling between unrelated parts of a codebase. Modern guidance often prefers Dependency Injection (from the SOLID chapter) to manage a single shared instance instead.

Factory Method delegates object creation to a dedicated method rather than calling new directly everywhere, useful when the exact class to instantiate depends on some condition or should be decided in one central place:

interface Shape { function draw() }
class Circle implements Shape { function draw() { print("Drawing circle") } }
class Square implements Shape { function draw() { print("Drawing square") } }

class ShapeFactory {
    static function create(type): Shape {
        if type == "circle": return new Circle()
        if type == "square": return new Square()
        throw Error("Unknown shape type")
    }
}

shape = ShapeFactory.create("circle") // caller doesn't need to know the concrete class name
shape.draw()

This keeps object-creation logic centralized and consistent with the Open/Closed Principle: adding a new shape means adding a new case in the factory and a new class, without touching every place that previously called new Circle() or new Square() directly.

Singleton and Factory Design Patterns Explained | DevNotes