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