Creational Design Patterns - Singleton and Builder

Publish date: 2024-07-03
Tags: System-Design, Software-Engineering

Design Pattern

Creational Design Patterns

Singleton

Builder


Singleton

Problems

Solution

Simple Singleton in Java

public class Database {
	private Database() {
	}
}
public class Database {
	private static Database instance = new Database();
	
	private Database() {
	}
	
	public static Database getInstance() {
		if (instance == null) {
			instance = new Database();
		}

		return instance;
	}
}

Thread-Safe Singleton in Java

public class Database {
	private static Database instance = new Database();
	
	private Database() {
	}
	
	public static synchronized Database getInstance() {
		if (instance == null) {
			instance = new Database();
		}

		return instance;
	}
}

Double-Checked Locking in Java

public class Database {
	private static Database instance = new Database();
	
	private Database() {
	}
	
	public static Database getInstance() {
		if (instance == null) {
			synchronized (Database.class) {
				if (instance == null) {
					instance = new Database();
				}
			}
		}

		return instance;
	}
}

Singleton Pattern in Python

class ConnectionPool(object):
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            print("Creating the object")
            cls._instance = super(ConnectionPool, cls).__new__(cls)
        return cls._instance
pool1 = ConnectionPool()
print(pool1)
Creating the object
<__main__.ConnectionPool object at 0x7f15ecf9fe80>
pool2 = ConnectionPool()
print(pool2)
print("Are they the same object?", pool1 is pool2)
<__main__.ConnectionPool object at 0x7f15ecf9fe80>
Are they the same object? True

Singleton Pattern in Golang

Using user-defined function
var lock = &sync.Mutex{}

type connectionPool struct {
}

var connPoolInstance *connectionPool

func GetInstance() *connectionPool {
    if connPoolInstance == nil {
        lock.Lock()
        defer lock.Unlock()
        if connPoolInstance == nil {
            fmt.Println("Creating Connection Pool Instance Now")
            connPoolInstance = &connectionPool{}
        } else {
            fmt.Println("Connection Pool Instance already created-1")
        }
    } else {
        fmt.Println("Connection Pool Instance already created-2")
    }
    return connPoolInstance
}
Using sync.Once
var once sync.Once

type connectionPool struct {
}

var connPoolInstance *connectionPool

func GetInstanceUsingSync() *connectionPool {
    if connPoolInstance == nil {
        once.Do(
            func() {
                fmt.Println("Creating Connection Pool Instance Now")
                connPoolInstance = &connectionPool{}
            })
    } else {
        fmt.Println("Connection Pool Instance already created-2")
    }
    return connPoolInstance
}

Builder

Problems

Solution

Using Inner Class in Java

public class Database {
	private String host;
	private int port;
	private String username;
	private String password;
 
	public Database(DatabaseParameters parameter) {
		this.host = parameter.host;
		this.port = parameter.port;
		this.username = parameter.username;
		this.password = parameter.password;
	}
}

class DatabaseParameters {
	public String host;
	public int port;
	public String username;
	public String password;
}
public class Database {
	private String host;
	private int port;
	private String username;
	private String password;
	 
	private Database() {
	}
	
	public static class DatabaseBuilder {
		private String host;
		private int port;
		private String username;
		private String password;
	 
		public Database build() {
			Database database = new Database();
			database.host = this.host;
			database.port = this.port;
			database.username = this.username;
			database.password = this.password;
			return database;
		}
	}
}
Database database = new Database.DatabaseBuilder()
	.host("localhost")
	.port(3306)
	.username("root")
	.password("password")
	.build();

Builder Pattern in Python

from __future__ import annotations
from abc import ABC, abstractmethod

class Builder(ABC):
    @abstractmethod
    def with_name(self, name: str) -> Builder:
        pass

    @abstractmethod
    def with_url(self, host: str, port: int) -> Builder:
        pass

    @abstractmethod
    def build(self) -> Database:
        pass

class DatabaseBuilder(Builder):
    def __init__(self) -> None:
        self._database = Database()

    def with_name(self, name: str) -> DatabaseBuilder:
        self._database._name = name
        return self
    
    def with_url(self, host: str, port: int) -> DatabaseBuilder:
        self._database._host = host
        self._database._port = port
        return self
    
    def build(self) -> Database:
        if not self.is_valid():
            raise ValueError("Invalid database configuration")
        return self._database
    
    def is_valid(self) -> bool:
        return self._database._name is not None


class Database:
    def __init__(self, name=None, host=None, port=None):
        self._name = name
        self._host = host
        self._port = port

    def __repr__(self):
        return f"Database(name={self._name}, host={self._host}, port={self._port})"
    
if __name__ == "__main__":
    try:
        # Building a valid database
        db = DatabaseBuilder().with_name("MyDB").with_url("localhost", 3306).build()
        print(db)

        # Building an invalid database (missing name)
        invalid_db = DatabaseBuilder().with_url("localhost", 3306).build()
        print(invalid_db)
    except ValueError as e:
        print(e)

Builder pattern in Golang

package builder

import (
	"errors"
)

type Database struct {
	name string
	host string
	port int
}

type DatabaseBuilder struct {
	database *Database
}

func NewDatabaseBuilder() *DatabaseBuilder {
	return &DatabaseBuilder{database: &Database{}}
}

func (b *DatabaseBuilder) WithName(name string) *DatabaseBuilder {
	b.database.name = name
	return b
}

func (b *DatabaseBuilder) WithUrl(host string, port int) *DatabaseBuilder {
	b.database.host = host
	b.database.port = port
	return b
}

func (b *DatabaseBuilder) Build() (*Database, error) {
	if !b.isValid() {
		return nil, errors.New("invalid database configuration")
	}
	return b.database, nil
}

func (b *DatabaseBuilder) isValid() bool {
	return b.database.name != ""
}

References: Singleton Pattern in Python

Singleton Pattern in Golang

These are my notes from the Low-Level Design (LLD) course I took at Scaler.

Check Python, Java and Go code on Github Repo

Tags: System-Design, Software-Engineering