Unlocking Object Creation: A Guide to Creational Design Patterns with Real-world Examples

Aayush Pagare
6 min readMay 13, 2024

Creational design patterns are a set of design patterns in software engineering that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation.

The Factory Pattern

The Factory Pattern is a creational design pattern used in object-oriented programming. It provides a way to create objects without specifying their exact class. Instead of directly instantiating objects, the factory pattern defines an interface or abstract class for creating objects, and subclasses or implementing classes are responsible for creating instances of the required class.

Let’s understand this by an example.

Let’s take an example where we are developing a external module or an npm package called as Vehicle.

Example : Developing without using the Factory Pattern.

//Vehicle.ts
class Vehicle {
constructor(public color: string) { this.color = color; }

honks() {
console.log(`vehicle honking`)
}
}

class TwoWheeler extends Vehicle { }

class FourWheeler extends Vehicle { }

class Bike extends TwoWheeler { }

class Car extends FourWheeler { }

class Truck extends FourWheeler { }

class Tempo extends FourWheeler { }
//Client.ts
enum VehicleType {
CAR = 'CAR',
TRUCK = 'TRUCK',
BIKE = 'BIKE',
TEMPO = 'TEMPO'
}

const getUserInput = () => {
//Logic to get user input
}
let vehicle: Vehicle

switch (getUserInput()) {
case VehicleType.CAR:
vehicle = new Car('blue')
break
case VehicleType.TRUCK:
vehicle = new Truck('green')
case VehicleType.BIKE:
vehicle = new Bike('green')
case VehicleType.TEMPO:
vehicle = new Tempo('green')
default:
throw new Error('Invalid vehicle type')
}

Here as we can see client has to write all the instantiation logic based on type of object it needs.

The problem with this is if in future we add a new class in our package’s code then client has to change its code too that’s a kind of tight coupling which could also break client’s existing code.

Therefore, it is suggested to wrap the instantiation logic inside a Factory class and provide directly to client

Example: With Factory Pattern

//Vehicle.ts
class Vehicle {
constructor(public color: string) { this.color = color; }

honks() {
console.log(`vehicle honking`)
}
}

class TwoWheeler extends Vehicle { }

class FourWheeler extends Vehicle { }

class Bike extends TwoWheeler { }

class Car extends FourWheeler { }

class Truck extends FourWheeler { }

class Tempo extends FourWheeler { }

enum VehicleType {
CAR = 'CAR',
TRUCK = 'TRUCK',
BIKE = 'BIKE',
TEMPO = 'TEMPO'
}

class VehicleFactory {
constructor(public vtype: VehicleType, public color: string) {
this.vtype = vtype
this.color = color
}

getVehicle() {
switch (this.type) {
case VehicleType.CAR:
return new Car(this.color)
case VehicleType.TRUCK:
return new Truck(this.color)
case VehicleType.BIKE:
return new Bike(this.color)
case VehicleType.TEMPO:
return new Tempo(this.color)
default:
throw new Error('Invalid vehicle type')
}
}
}
//client.ts
const vehicle = new VehicleFactory(getUserInput(), 'green')

As we can see now client has to just write one line of code to get the desired object from our library class. This client code is loosely coupled with our package’s code.

In these way creational design patterns solves some common day to day design problems in Software development.

The Singleton Pattern

The Singleton Pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. In other words, it restricts the instantiation of a class to a single object.

Key features of the Singleton Pattern:

  1. Private Constructor: The Singleton class typically has a private constructor to prevent direct instantiation of objects from outside the class.
  2. Static Instance: The Singleton class provides a static method that returns the same instance of the class every time it is called. This instance is usually stored as a private static member variable within the class.
  3. Lazy Initialization (optional): The Singleton instance can be created lazily (i.e., when it is first requested) or eagerly (i.e., when the class is loaded). Lazy initialization is often used to improve performance and resource usage by deferring the creation of the Singleton instance until it is actually needed.
  4. Thread Safety (optional): If the Singleton pattern is used in a multithreaded environment, precautions must be taken to ensure that the Singleton instance is created safely and that multiple threads cannot create multiple instances simultaneously. This can be achieved using techniques such as double-checked locking, synchronization, or using the “Initialization-on-demand holder idiom” in languages that support it.

Let’s look into it with an example:

In most of our Backend applications we need to connect it with some Database like mongodb or postgres sql, and we need to connect it only once when our application is bootstrapped. Also, we want to use that connection object throughout out application. This is a common design use case where Singleton pattern is useful.

class DatabaseConnection { 
//A static instance
private static connection: DatabaseConnection | null = null

public static getConnection() {
//Lazy initialization. we will not create further objects if one is alredy created.
if(this.connection) {
return this.connection;
} else {
return this.connection = new DatabaseConnection();
}

}

//A private constructor, we don't want client to create more than one object
private constructor() { }
}


const c1 = DatabaseConnection.getConnection();

const c2 = DatabaseConnection.getConnection();

console.log(c1 === c2); //true.
//only one connection instance used throughout the project.

Note: You can use mutex locks to ensue thread safety in multi-threading languages.

The Builder Pattern

The Builder Pattern is a creational design pattern that is used to construct complex objects step by step. It separates the construction of a complex object from its representation, allowing the same construction process to create different representations. This pattern is particularly useful when an object has a large number of parameters or configuration options, making the constructor or initialization method cumbersome and difficult to manage.

Let’s take an example to understand this.

We are developing an application where we need some of user details but most of them are optional while registration process of user. So, to fill up the constructor with numerous optional params is a bad practice. Instead, what we can do is only allow required properties in the constructor and rest must be set by builder class.

class User {
name: string;
age?: number;
email?: string;
married?: 'yes' | 'no'
working?: 'yes' | 'no'
spouseName?: string;

constructor(name: string) {
this.name = name;
}
}

class UserBuilder {
private user: User;

constructor(name: string) {
this.user = new User(name);
}

setAge(age: number) {
this.user.age = age;
return this;
}

setEmail(email: string) {
this.user.email = email;
return this;
}

setMarried(spouseName: string) {
this.user.spouseName = spouseName;
this.user.married = true;
return this;
}
build() {
return this.user
}
}

//In some part of code
const user = new UserBuilder("Aayush")
.setAge(22)
.setEmail("aayushpagare21@gmail.com")
.serMarried("Julie S.")
.build()

To summarize it up for you:

  • Factory Pattern : In software development, it’s common to encounter scenarios where the exact class of objects to be created is not known in advance or may need to be determined dynamically at runtime. Directly instantiating objects using new can lead to tight coupling between client code and concrete classes, making the code less flexible and harder to maintain.
  • Why Use It: The Factory Pattern addresses this issue by providing a centralized mechanism for creating objects without specifying their exact class. By defining an interface or abstract class for object creation and delegating the instantiation logic to subclasses or implementing classes, the Factory Pattern promotes loose coupling, flexibility, and extensibility in object creation.
    ss the application.
  • Singleton Pattern : In some software systems, it’s necessary to ensure that a class has only one instance and that this instance is globally accessible. This could be for reasons such as managing a shared resource, maintaining global state, or controlling access to a unique system component.
  • Why Use It: The Singleton Pattern addresses this requirement by restricting the instantiation of a class to a single object and providing a global point of access to that instance. By ensuring that only one instance of the class exists and providing a static method to access it, the Singleton Pattern promotes centralized control, efficient resource management, and consistency in object state across the application.
  • Builder Pattern : In scenarios where objects have a large number of optional parameters or configuration options, constructing them using a single constructor with numerous parameters can be unwieldy and error prone. Moreover, the order in which parameters are specified may not always be consistent or intuitive.
  • Why Use It: The Builder Pattern addresses this issue by separating the construction of a complex object from its representation, allowing the same construction process to create different representations. By providing a step-by-step approach to constructing objects and allowing clients to specify only the desired parameters, the Builder Pattern promotes clarity, flexibility, and maintainability in object construction.

--

--

Aayush Pagare

System design and coding enthusiast, tackling tech challenges with passion and dedication.