I'm always excited to take on new projects and collaborate with innovative minds.
contact@niteshsynergy.com
https://www.niteshsynergy.com/
Welcome To Design Pattern Blog By Nitesh Synergy
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.
Reusability – Save time by using tried-and-tested solutions.
Scalability – Help build flexible, easily extendable code.
Maintainability – Code becomes easier to understand and update.
Standardization – Brings a common vocabulary for developers.
Avoid Re-inventing the Wheel – Use known solutions for known problems.
Design patterns are broadly classified into 3 main categories:
Used when object creation is complex or needs to be controlled.
Pattern Name | Purpose |
---|---|
Singleton | Only one instance of a class. |
Factory Method | Create objects without exposing the exact class. |
Abstract Factory | Factory of related factories. |
Builder | Step-by-step object creation. |
Prototype | Clone existing objects. |
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.
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.
Use Singleton when:
Single Configuration Across App
E.g., App-wide configuration manager or settings file.
Database Connection Pool
Only one connection manager instance to handle all DB operations.
Logging
Centralized logger instance to log from any part of the app.
Caching
Central cache store used throughout application.
Thread Pool
Single pool used by multiple threads.
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.
Avoid Singleton when:
Unit Testing Needed
Singleton hides dependencies, making mocking hard.
Multi-threading if not handled properly
Poor implementation can lead to race conditions.
Global State is a Problem
Leads to tight coupling between classes.
Requires Multiple Instances
E.g., Different connections for different databases.
Rules For Singleton Pattern:
Ensure the class constructor is private to prevent instantiation from outside the class.
Create a static (ideally final
) variable to hold the single instance of the class.
Provide a public static method or property that returns the same instance every time it's called.
In multithreaded environments, ensure that the instance creation logic is thread-safe to avoid multiple instances being created.
Override the cloning behavior to avoid duplication of the singleton object via clone()
.
Implement mechanisms (like readResolve()
in Java) to prevent deserialization from creating a new instance.
Add checks inside the constructor to throw an exception if an instance already exists, thus protecting against reflection-based instantiation.
Use a static block to eagerly initialize the singleton and include logic to prevent reflection-based violations if needed.
final
KeywordMark the instance variable as final
to ensure immutability and prevent reassignment after initialization.
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):
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
}
}
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)
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)
→ 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;
}
}
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
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
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
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
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
Domain | Singleton Class Example | Purpose |
---|---|---|
Banking | TransactionManager | Maintain consistency and log transactions |
JDBC/Backend | DatabaseConnectionManager | Reuse DB connections, optimize resource usage |
Gaming | GameEngine / GameSettings | Global settings and logic across game screens |
AI Tools | AIModelManager / APIClient | Centralized model config and API management |
GameEngineSingleton.java
— Perfect Singleton for Game Engine