Skip to content

Singleton

'''
Imagine you're on a plane, there is a control tower which decides which plane goes on which airway. 
There are multiple planes, but only 1 control tower. If they had multiple control tower and let's say Flight A is on Runway1 which is allotted by ControlTower1
and ControlTower2 allots Flight B also on Runway1 (because it doesn't know about ControlTower1) then planes will crash. 

It's harder to manage multiple ControlTowers, and hence a singleton pattern (1 Centralised Control Tower to decide which plane goes where) is what we need here to solve this problem.

Singleton: You declare a private instance and if it hasn't been created you create else you return the already created instance of the class.
'''

import sys
# without Singleon (Plane Crash)
class ControlTower:
    def __init__(self):
        print("Iniatilising Control Tower")

Tower1 = ControlTower()
Tower2 = ControlTower()
print("Singleton memory:", sys.getsizeof(Tower1.__dict__), "bytes") # 288 bytes, we are saving 288 bytes each time we call this because of singleton

if Tower1 is Tower2:
    print("Planes will not crash since they are handled by a centralised Control Tower")
else:
    print("Planes will crash")

print("*" * 60)
# Singleton pattern
class ControlTowerSingleton:
    _instance = None # pvt variable
    def __new__(cls):
        if not cls._instance:
            cls._instance = super().__new__(cls)
            print("Iniatilising Control Tower")
        return cls._instance

Tower1_singleton = ControlTowerSingleton()
Tower2_singleton = ControlTowerSingleton()

if Tower1_singleton is Tower2_singleton:
    print("Planes will not crash since they are handled by a centralised Control Tower")
else:
    print("Planes will crash")

print("\n")
print("*" * 60)
# More examples for practice.
# DB making a connection repetitive amounts of time, better to make the connection once and then do whatever SQL commands you want. Saves a lot of memory

'''
Without Singleton: Each DatabaseConnection() would create a new connection, wasting memory and time.
With Singleton: First call creates the connection. Later calls just reuse it.
'''

import sqlite3 as sql


class DBConnection:
    _instance = None
    def __new__(cls):
        if not cls._instance:
            print("Creating new DB connection")
            cls._instance = super().__new__(cls)
        else:
            print("Reusing existing DB connection")
        return cls._instance

    def query(self, sql):
        print(f"Executing query: {sql}")

db1 = DBConnection()
db2 = DBConnection()
if db1 is db2:
    print("Singleton Pattern is being used here")
db1.query("SELECT * FROM users;")
db2.query("INSERT INTO logs VALUES ('test');")


print("\n")
print("*" * 60)
# Logging, declare it once so you don't have to open and create new file for logging each time you want to.

'''
Without Singleton: If you create new loggers each time, each might open the file separately → performance hit + duplicated logs.
With Singleton: First logger sets up the file handler. Every other call just reuses the same instance.
'''

import logging
class Logger:
    _instance = None
    def __new__(cls):
        if not cls._instance:
            print("Creating new logger")
            cls._instance = super().__new__(cls)
            cls._instance.logger = logging.getLogger(__name__)
            cls._instance.logger.setLevel(logging.INFO)
        else:
            print("Reusing existing logger")
        return cls._instance

    def log(self, message):
        self.logger.info(message)
        print(message)

logger1 = Logger()
logger2 = Logger()

if logger1 is logger2:
    print("Singleton Pattern is being used here")
else:
    print("Singleton pattern not being used")