I'm always excited to take on new projects and collaborate with innovative minds.

Email

contact@niteshsynergy.com

Website

https://www.niteshsynergy.com/

Design Pattern

Welcome To Design Pattern Blog By Nitesh Synergy

✅ What is a Design Pattern?

A design pattern is a proven, reusable solution to a common problem in software design. It is not a complete code, but a template or guideline on how to solve specific design issues in object-oriented software.

Think of design patterns like blueprints—software architects use them to build robust, maintainable, and scalable applications.

 

🎯 Why Are Design Patterns Needed?

  1. Reusability – Save time by using tried-and-tested solutions.

  2. Scalability – Help build flexible, easily extendable code.

  3. Maintainability – Code becomes easier to understand and update.

  4. Standardization – Brings a common vocabulary for developers.

  5. Avoid Re-inventing the Wheel – Use known solutions for known problems.

 

🧱 Types of Design Patterns

Design patterns are broadly classified into 3 main categories:

1. Creational Patterns (Object creation)

Used when object creation is complex or needs to be controlled.

Pattern NamePurpose
SingletonOnly one instance of a class.
Factory MethodCreate objects without exposing the exact class.
Abstract FactoryFactory of related factories.
BuilderStep-by-step object creation.
PrototypeClone existing objects.

 

  1. Singleton Pattern

✅ What is Singleton Pattern?

Singleton is a creational design pattern that ensures:

  • Only one instance of a class exists throughout the application.

  • Provides a global point of access to that instance.


✅ Why Singleton is Important?

  • Resource Control: Controls access to resources like DB connections, configuration files, or loggers.

  • Memory Efficiency: Avoids creation of multiple objects of the same class unnecessarily.

  • Consistency: Ensures consistent state/data across the system.


✅ When to Use Singleton? (Use Cases)

Use Singleton when:

  1. Single Configuration Across App

    • E.g., App-wide configuration manager or settings file.

  2. Database Connection Pool

    • Only one connection manager instance to handle all DB operations.

  3. Logging

    • Centralized logger instance to log from any part of the app.

  4. Caching

    • Central cache store used throughout application.

  5. Thread Pool

    • Single pool used by multiple threads.

     

✅ Real-Time Project Example

🔹 Project: E-commerce Application

  • Singleton Use:

    • A Logger class logs order activity, errors, payments.

    • DatabaseConnector class manages connections to the MySQL DB.

    • AppConfig class loads settings like tax %, currencies, and uses it across services.


❌ When Not to Use Singleton

Avoid Singleton when:

  1. Unit Testing Needed

    • Singleton hides dependencies, making mocking hard.

  2. Multi-threading if not handled properly

    • Poor implementation can lead to race conditions.

  3. Global State is a Problem

    • Leads to tight coupling between classes.

  4. Requires Multiple Instances

    • E.g., Different connections for different databases.

 

Rules For Singleton Pattern:

🔒 1. Private Constructor

Ensure the class constructor is private to prevent instantiation from outside the class.

 

📦 2. Static Instance Variable

Create a static (ideally final) variable to hold the single instance of the class.

 

🌍 3. Global Access Point

Provide a public static method or property that returns the same instance every time it's called.

 

🧵 4. Thread Safety

In multithreaded environments, ensure that the instance creation logic is thread-safe to avoid multiple instances being created.

 

❌ 5. Prevent Cloning

Override the cloning behavior to avoid duplication of the singleton object via clone().

 

🔐 6. Serialization Safety

Implement mechanisms (like readResolve() in Java) to prevent deserialization from creating a new instance.

 

🪞 7. Reflection Safety

Add checks inside the constructor to throw an exception if an instance already exists, thus protecting against reflection-based instantiation.

 

⚡ 8. Static Block for Eager Initialization

Use a static block to eagerly initialize the singleton and include logic to prevent reflection-based violations if needed.

 

🛡️ 9. Use of final Keyword

Mark the instance variable as final to ensure immutability and prevent reassignment after initialization.

 

📌 Guidelines for Using Singleton

  • Use Only When Necessary: Singleton introduces global state — use it only when a single instance is essential (e.g., config, logging).

  • Avoid Overuse: Overuse leads to tight coupling and makes testing harder.

  • Ensure Thread Safety: Always ensure thread-safe implementation in concurrent environments.

  • Testing Considerations: Mock singleton objects during unit testing to isolate dependencies.

  • Lazy vs Eager Initialization:

    • Use lazy initialization when the object is heavy and not always needed.

    • Use eager initialization when the object is lightweight or always needed.

 

Ways To Achieve Singleton Pattern:

Sol-1:
Basic Singleton Implementation (Eager Initialization):

  • Advantages: Simple, thread-safe without additional synchronization.
  • Disadvantages: Instance is created at class loading, even if it’s never used.
public class Singleton {
   private static final Singleton INSTANCE = new Singleton(); // Static instance
   
   private Singleton() {
       // Private constructor
   }
   
   public static Singleton getInstance() {
       return INSTANCE; // Global access point
   }
}

 

Sol-2:

Lazy Initialization

  • Advantages: Instance is created only when required.
  • Disadvantages: Not thread-safe.
public class Singleton {
   private static Singleton instance;
   
   private Singleton() {
       // Private constructor
   }
   
   public static Singleton getInstance() {
       if (instance == null) {
           instance = new Singleton();
       }
       return instance;
   }
}

Sol-3:

Thread-Safe Singleton (Synchronized Method)

  • advantages: Thread-safe.
  • Disadvantages: Performance overhead due to synchronized method.
public class Singleton {
   private static Singleton instance;
   
   private Singleton() {
       // Private constructor
   }
   
   public static synchronized Singleton getInstance() {
       if (instance == null) {
           instance = new Singleton();
       }
       return instance;
   }
}

Sol-4:

Thread-Safe Singleton (Double-Checked Locking)

  • Advantages: Efficient, avoids unnecessary synchronization after initialization.
  • Disadvantages: Requires understanding of volatile keyword.

For all keywords -https://www.niteshsynergy.com/java

public class Singleton {
   private static volatile Singleton instance;
   
   private Singleton() {
       // Private constructor
   }
   
   public static Singleton getInstance() {
       if (instance == null) {
           synchronized (Singleton.class) {
               if (instance == null) {
                   instance = new Singleton();
               }
           }
       }
       return instance;
   }
}

Sol-5:

Reflection Safety:

public class Singleton {
   private static Singleton instance;
   private Singleton() {
       // Prevent Reflection from creating another instance
       if (instance != null) {
           throw new IllegalStateException("Singleton instance already created!");
       }
   }
   public static Singleton getInstance() {
       if (instance == null) {
           synchronized (Singleton.class) {
               if (instance == null) {
                   instance = new Singleton();
               }
           }
       }
       return instance;
   }
}

Sol-6:

Singleton with Enum (Best Practice):

// Singleton Enum for Logging Utility
public enum Logger {
   INSTANCE;
   // Method to log messages
   public void log(String message) {
       System.out.println("LOG: " + message);
   }
   // Method to log errors
   public void logError(String errorMessage) {
       System.err.println("ERROR: " + errorMessage);
   }
}
public class UserService {
   // Some user-related logic
   public void createUser(String username) {
       // Log an informational message
       Logger.INSTANCE.log("User creation started for: " + username);
       
       try {
           // Simulating user creation logic
           if (username == null || username.isEmpty()) {
               throw new IllegalArgumentException("Username cannot be null or empty");
           }
           // Simulate user created
           Logger.INSTANCE.log("User created successfully: " + username);
       } catch (Exception e) {
           // Log the error using Singleton Logger
           Logger.INSTANCE.logError("Error creating user: " + e.getMessage());
       }
   }
}

Sol-7:

 Preventing Cloning via the clone() Method:

public class Singleton {
   private static Singleton instance;
   private Singleton() {
       if (instance != null) {
           throw new IllegalStateException("Singleton instance already created!");
       }
   }
   public static Singleton getInstance() {
       if (instance == null) {
           synchronized (Singleton.class) {
               if (instance == null) {
                   instance = new Singleton();
               }
           }
       }
       return instance;
   }
   @Override
   public Object clone() {
       throw new CloneNotSupportedException("Cloning of Singleton is not allowed!");
   }
}

 

Sol-8:

 Preventing Serialization Attack:

public class Singleton implements Serializable {
   private static final long serialVersionUID = 1L;
   private static Singleton instance;
   private Singleton() {
       if (instance != null) {
           throw new IllegalStateException("Singleton instance already created!");
       }
   }
   public static Singleton getInstance() {
       if (instance == null) {
           synchronized (Singleton.class) {
               if (instance == null) {
                   instance = new Singleton();
               }
           }
       }
       return instance;
   }
   // Ensure Singleton instance is preserved during serialization/deserialization
   protected Object readResolve() {
       return instance;
   }
}

 

Sol-9:

Using a Static Block (Eager Initialization with Reflection Safety):

public class Singleton {
   private static final Singleton instance;
   static {
       try {
           instance = new Singleton();
       } catch (Exception e) {
           throw new IllegalStateException("Singleton instance already created!");
       }
   }
   private Singleton() {
       // Prevent Reflection from creating another instance
       if (instance != null) {
           throw new IllegalStateException("Singleton instance already created!");
       }
   }
   public static Singleton getInstance() {
       return instance;
   }
}

 

Sol-10:

 Use of final Keyword:

public class Singleton {
   private static final Singleton instance;
   static {
       instance = new Singleton();
   }
   private Singleton() {
       // Prevent Reflection from creating another instance
       if (instance != null) {
           throw new IllegalStateException("Singleton instance already created!");
       }
   }
   public static Singleton getInstance() {
       return instance;
   }
}

→There are many other option to create Singleton class.

Below code for Singleton breaking ways:

//Code to break: 1. Reflection
class BreakWithReflection {
   public static void breakWithReflection() {
       try {
           Singleton instance1 = Singleton.getInstance();
           Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
           constructor.setAccessible(true); // Bypass private access
           Singleton instance2 = constructor.newInstance();
           System.out.println(" From breakWithReflection");
           System.out.println(instance1.hashCode());
           System.out.println(instance2.hashCode());
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}

//2. Serialization/Deserialization way break
class BreakWithSerialization {
   public static void breakWithSerialization() throws Exception {
       Singleton instance1 = Singleton.getInstance();
       // Serialize instance
       ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
       out.writeObject(instance1);
       out.close();
       // Deserialize instance
       ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
       Singleton instance2 = (Singleton) in.readObject();
       in.close();
       System.out.println(" From breakWithSerialization");
       System.out.println(instance1.hashCode());
       System.out.println(instance2.hashCode());
   }
}

// 3. Cloning way break
class BreakWithCloning {
   public static void breakWithCloning() throws CloneNotSupportedException {
       Singleton instance1 = Singleton.getInstance();
       Singleton instance2 = (Singleton) instance1.clone(); // If clone() is accessible
       System.out.println(" From breakWithCloning");
       System.out.println(instance1.hashCode());
       System.out.println(instance2.hashCode());
   }
}

// 4. Multithreading (Race Condition)
class BreakWithMultithreading {
   public static void breakWithMultithreading() {
       Runnable task = () -> {
           Singleton instance = Singleton.getInstance();
           System.out.println(instance.hashCode());
       };
       Thread thread1 = new Thread(task);
       Thread thread2 = new Thread(task);
       System.out.println(" From BreakWithMultithreading");
       thread1.start();
       thread2.start();
   }
}

//5. Garbage Collection
class BreakWithGarbageCollection {
   public static void breakWithGarbageCollection() {
       Singleton instance = Singleton.getInstance();
       WeakReference<Singleton> weakRef = new WeakReference<>(instance);
       instance = null; // Nullify the strong reference
       System.gc(); // Force garbage collection
       System.out.println(" From breakWithGarbageCollection");
       Singleton newInstance = Singleton.getInstance();
       System.out.println(weakRef.get() == newInstance); // False if GC removed the weak reference
   }
}

//6. Subclassing
class BreakWithSubclassing {
   public static void breakWithSubclassing() {
    //   Singleton subclassInstance = new Singleton() {}; // Anonymous subclass
   //    System.out.println(subclassInstance.hashCode());
   }
}

class Singleton{
   private static Singleton instance;
   private Singleton(){
   }
   public static Singleton getInstance() {
       if (instance == null) {
           instance = new Singleton();
       }
       return instance;
   }
   
   // // If clone() is accessible, // 3. Cloning way break
   @Override
   public Object clone() throws CloneNotSupportedException {
       return super.clone();
   }
   
   public void showMessage() {
       System.out.println("Singleton Instance");
   }
}
package com.niteshsynergy.designPattern.CreationalDesign;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
public class Demo1 {
   public static void main(String[] args) throws Exception {
     /*  Singleton s1 = Singleton.getInstance();
       System.out.println(s1.hashCode());
       Singleton s2 = Singleton.getInstance();
       System.out.println(s2.hashCode());
       */
       BreakWithReflection.breakWithReflection();
       BreakWithSerialization.breakWithSerialization();
       BreakWithCloning.breakWithCloning();
       BreakWithMultithreading.breakWithMultithreading();
       BreakWithGarbageCollection.breakWithGarbageCollection();
       BreakWithSubclassing.breakWithSubclassing();
   }
   //Prevention Strategies:
   //Prevent Reflection: Add a check in the private constructor.
  /* private Singleton() {
       if (instance != null) {
           throw new IllegalStateException("Instance already created!");
       }
   }*/
   //Serialization Protection: Implement readResolve().
 /*  protected Object readResolve() {
       return instance;
   }*/
   //Prevent Cloning: Override clone() and throw an exception.
   //java
   //Copy code
   @Override
   protected Object clone() throws CloneNotSupportedException {
       throw new CloneNotSupportedException("Cannot clone singleton");
   }
   //Thread-Safe Initialization: Use Bill Pugh Singleton or Double-Checked Locking.
   //Final Class: Declare the class as final to prevent subclassing.

}

✅ Real-World Use Cases of Singleton Class

🏦 1. Banking System Use Case

Use Case: Centralized Transaction Manager

  • In a banking application, all deposit, withdrawal, and transfer operations must go through a single TransactionManager to maintain consistency.

  • This manager must be a Singleton to ensure that all operations are logged and validated through one point—preventing data inconsistencies in concurrent environments.

🔹 Why Singleton?

  • Ensures one version of the transaction flow logic

  • Prevents double processing or conflicting operations


🔗 2. JDBC Connection Pool (Database Access Layer)

Use Case: DatabaseConnectionManager Singleton

  • In any Java app (or backend API), database operations use JDBC.

  • A DatabaseConnectionManager class can be Singleton so it maintains a single connection pool reused by all DAOs (Data Access Objects).

🔹 Why Singleton?

  • Saves system resources

  • Manages connection limits efficiently

  • Reduces overhead of repeatedly opening/closing connections


🎮 3. Gaming Application

Use Case: GameSettings or GameEngine Singleton

  • In a game, GameSettings (like resolution, sound level, difficulty level) are set once and used across multiple levels/screens.

  • Or the GameEngine managing game loop, rendering, and physics should only be one across the app.

🔹 Why Singleton?

  • Consistent behavior across game scenes

  • Prevents re-initializing core game logic

  • Shares resources like physics engine, AI logic across levels


🤖 4. AI/ChatGPT-Based App Use Case

Use Case: AIModelManager or APIClientManager

Imagine building an AI-based tool like ChatGPT:

  • You integrate OpenAI or a custom LLM into your system.

  • A Singleton class (e.g., AIModelManager) ensures the prompt formatting, token handling, or rate-limit logic is handled centrally.

  • Or a Singleton APIClient manages the HTTP connection, headers, and authorization tokens.

🔹 Why Singleton?

  • Avoids multiple heavy model initializations

  • Centralized control over API call logic

  • Manages rate limits, retries, and tokens from one place

  • Reduces latency by reusing configuration and headers

 

📌 Summary Table

DomainSingleton Class ExamplePurpose
BankingTransactionManagerMaintain consistency and log transactions
JDBC/BackendDatabaseConnectionManagerReuse DB connections, optimize resource usage
GamingGameEngine / GameSettingsGlobal settings and logic across game screens
AI ToolsAIModelManager / APIClientCentralized model config and API management

 

 

23 min read
Jun 12, 2025
By Nitesh Synergy
Share