মূল বিষয়বস্তুতে যান
Clean Code Mastery

Singleton Pattern: পুরো স্কুলে একজনই হেড স্যার

স্কুলের হেড স্যারের গল্প দিয়ে Singleton design pattern বোঝো — সহজ TypeScript ও C# কোড, thread safety, আর কেন অনেক সিনিয়র ডেভেলপার এটাকে anti-pattern বলেন।

23 মিনিট আপডেট: June 11, 2026beginner
design-patternscreational-patternssingletonglobal-statethread-safetydependency-injectiontypescriptcsharp

এক স্কুল, একজনই হেড স্যার

ধরো তুমি মতিঝিল মডেল স্কুল-এ পড়ো। হাজারো ছাত্র, শত শত শিক্ষক, বিশাল বিল্ডিং — কিন্তু হেড স্যার কতজন?

একজন। তার নাম জামাল স্যার

একজনই হেড স্যার, নিচতলায় একটাই অফিস, আর সেই অফিসের সামনে একটাই নোটিশ বোর্ড। স্কুলের যেকোনো দরকারে — পরীক্ষার তারিখ, ছুটির ঘোষণা, বার্ষিক অনুষ্ঠানের অনুমতি — সবাই সেই একই অফিসে যায়। নাসরিন ম্যাডাম (গণিত) যান। তারিক স্যার (খেলাধুলা) যান। ক্লাস সিক্স-এর ছোট্ট ফাতেমাও যায় বিজ্ঞান ক্লাবের অনুমতি নিতে।

কেউ বলে না, "আমি আমার ক্লাসের জন্য আলাদা একজন হেড স্যার রাখব।" দুজন হেড স্যার দুটো আলাদা পরীক্ষার তারিখ দিলে কী হবে? পুরো স্কুল তছনছ!

তিনটা জিনিস লক্ষ্য করো এই ব্যবস্থায়:

  1. তুমি নিজে হেড স্যার তৈরি করতে পারবে না। ফাতেমা চাইলেই নতুন হেড স্যার আনতে পারবে না। পদটা সুরক্ষিত।
  2. পৌঁছানোর একটাই পথ। সবাই জানে অফিসটা কোথায়। আলাদা করে পরিচয় করিয়ে দেওয়া লাগে না।
  3. যে-ই যাক, একই হেড স্যারকে পাবে। ক্লাস সিক্স আর ক্লাস টেন — দুজনেই একই মানুষের কাছে যায়।

এটাই বাস্তব জীবনে Singleton pattern। পুরো program-এর জন্য একটাই বিশেষ object, তৈরি হওয়া কঠোরভাবে নিয়ন্ত্রিত, আর একটাই "দরজা" যেটা দিয়ে সবাই সেটায় পৌঁছায়।

আরেকটা উদাহরণ দিই। ধরো তোমাদের বাসার ছাদে একটাই পানির ট্যাংক। ফ্ল্যাট ১০১ আর ফ্ল্যাট ৫০৪ আলাদা কল খোলে, কিন্তু পানি আসে একই ট্যাংক থেকে। ফ্ল্যাট ৩০২ পানি নষ্ট করলে সব ফ্ল্যাটে টের পাওয়া যায়। কিছু জিনিস ঠিক তখনই সঠিক হয় যখন মাত্র একটাই থাকে — ট্যাংক, হেড স্যার, বিদ্যুৎ মিটার, টিমের ক্যাপ্টেন।

দেখো একটা সাধারণ দিনে কীভাবে সবাই একই অফিসে আসে:

চিত্র ১: একটাই অফিস, অনেক দর্শনার্থী, একই হেড স্যার

এই গল্পটা মনে রেখো। একটু পরেই এই হেড স্যারের অফিসটা কোডে লিখব। আর সবশেষে — এটা গুরুত্বপূর্ণ — সৎভাবে বলব কেন অনেক সিনিয়র ডেভেলপার বলেন, "এই pattern-টা খুব সাবধানে ব্যবহার করো।"

Singleton pattern কী?

Singleton pattern হলো একটা creational design pattern। সহজ কথায়:

Singleton নিশ্চিত করে পুরো program-এর জীবনকালে একটা class-এর মাত্র একটাই instance থাকবে, আর সেই instance-এ পৌঁছানোর জন্য একটাই global access point থাকবে — সাধারণত getInstance() নামের একটা static method।

তাহলে class-টা একই সাথে দুটো কাজ করে:

  • নিজের তৈরি হওয়া নিজেই নিয়ন্ত্রণ করে। Constructor-টা private করা থাকে, ফলে বাইরের কোড new Principal() লিখতে পারে না। Class নিজেই তার একটাই instance তৈরি করে।
  • Access দেয়। একটা static method সেই একটাই instance যে-ই চায় তাকে দিয়ে দেয়, program-এর যেকোনো জায়গা থেকে।
💡

এক লাইনে মনে রাখার কৌশল: "এক পদ, এক মানুষ, এক দরজা।" Singleton = private constructor + static instance + public getInstance()

Singleton হলো Gang of Four বইয়ের (১৯৯৪) ২৩টা classic pattern-এর একটা। ব্যাপারটা হলো এটা একদিকে সবচেয়ে সহজে শেখার মতো pattern, আবার এটাই সবচেয়ে বেশি সমালোচিত pattern — আজকে দুটো দিকই দেখব।

কলেজের জন্য একটু বেশি: লক্ষ্য করো যে Singleton ডিজাইনের কারণেই Single Responsibility Principle লঙ্ঘন করে। Class একই সাথে নিজের জীবনকাল পরিচালনা করে (কাজ ১) আর আসল কাজ করে (কাজ ২)। এই দ্বৈত দায়িত্বই পরে বেশিরভাগ সমস্যার কারণ। জীবনকাল পরিচালনাটা application-এর composition root বা container-এর হওয়া উচিত। এই কথাটা মনে রেখো — anti-pattern section-এ ফিরে আসবে।

কোন সমস্যার সমাধান করে

কেউ এমন অদ্ভুত class কেন বানাবে? দুটো আসল সমস্যা ডেভেলপারদের এদিকে নিয়ে আসে।

সমস্যা ১: কিছু জিনিস মাত্র একটাই থাকতে পারে।

ধরো একটা program-এ প্রতিটা অংশ নিজের database connection pool তৈরি করছে:

// The dangerous "everyone makes their own" way
class ReportService {
  private pool = new ConnectionPool(); // pool #1 (100 connections)
}
 
class BillingService {
  private pool = new ConnectionPool(); // pool #2 (another 100!)
}
 
class EmailService {
  private pool = new ConnectionPool(); // pool #3 (!!)
}
// The database planned for 100 connections... now faces 300.
// It starts rejecting connections. The app crashes at peak time.

একটা সাধারণ constructor দ্বিতীয় call-এ "না" বলতে পারে না। প্রতিটা new একটা নতুন object দেয়। কিন্তু কিছু resource-এর জন্য — log file, parsed config, connection pool, cache — দ্বিতীয় copy শুধু অপচয় না, এটা ভুল। দুটো log object একই file-এ লিখলে তাদের লাইনগুলো মিশে যাবে। দুটো settings object একে অপরের সাথে দ্বিমত করবে। দুজন হেড স্যার দুটো আলাদা পরীক্ষার তারিখ ঘোষণা করবে।

সমস্যা ২: সেই একটাই জিনিসকে অনেক জায়গা থেকে পৌঁছানো দরকার।

Logger প্রায় প্রতিটা file-এই দরকার। পঞ্চাশটা layer-এর constructor-এর মধ্য দিয়ে হাতে হাতে পাঠানো ক্লান্তিকর মনে হয়। Global variable পৌঁছানো সহজ হবে — কিন্তু global variable ভঙ্গুর: যেকোনো কোড সেগুলো overwrite করতে পারে।

Singleton একটাই আঘাতে দুটো সমস্যার উত্তর দেওয়ার চেষ্টা করে: constructor লুকাও (যাতে একটাই instance থাকে) আর একটা static দরজা দাও (যাতে সবাই পৌঁছাতে পারে)।

চিত্র ২: Singleton ছাড়া প্রতিটা caller নিজের copy বানায়; Singleton সহ সবাই একটাই share করে

অপচয়টা কতটা বড় সেটা দেখো — ১০টা service নিজের pool বানালে বনাম একটাই share করলে:

চিত্র ৩: ১০টা service connection pool ব্যবহার করলে resource-এর পার্থক্য

দশটা service প্রতিটা ১০০-connection pool খুললে এক হাজার connection — database সাইজ করা একশোর জন্য। Shared pool সেটাকে ঠিক একশোতেই রাখে। এই chart-টাই "মাত্র একটাই instance" ধারণার পুরো কারণ।

কীভাবে কাজ করে, ধাপে ধাপে

রেসিপিটা ছোট। যেকোনো ভাষায় পাঁচটা ধাপে লেখা যায়:

  1. Constructor-কে private করো। এটা সামনের দরজা লক করে দেয়। বাইরের কোড আর new Principal() লিখতে পারবে না — compiler নিজেই refuse করবে।
  2. Class-এর ভেতরে একটা private static field রাখো একটাই instance ধরে রাখার জন্য। শুরুতে null থাকে।
  3. একটা public static method বানাও — সাধারণত getInstance() নামে — যেটা সবাই call করবে।
  4. getInstance()-এর ভেতরে lazily তৈরি করো। প্রথম call-এ instance নেই, তাই তৈরি করে static field-এ রাখো। পরের প্রতিটা call-এ শুধু সেটা return করো। "Lazy" মানে প্রথম দরকারের সময়ই তৈরি হয়, program শুরুতে না।
  5. দরকার হলে thread-safe করো। দুটো thread একই মুহূর্তে getInstance() ডাকলে দুটো instance তৈরি হতে পারে — পুরো প্রতিশ্রুতি ভেঙে দিয়ে! ভাষাগুলো এর জন্য নিরাপদ tool দেয় (নিচে C#-এর Lazy<T> দেখব)।
চিত্র ৪: Singleton pattern-এর class structure

মজার self-arrow-টা দেখো: class একটা static field-এ নিজেরই একটা instance ধরে রাখে। এটাই Singleton-এর চেহারা।

দুজন শিক্ষক একে একে আসলে কী হয়:

চিত্র ৫: প্রথম call তৈরি করে, দ্বিতীয় call reuse করে

একটা singleton-এর জীবনকালে মাত্র তিনটা মুহূর্ত — এখনো তৈরি হয়নি, প্রথম চাহিদায় তৈরি হলো, তারপর চিরকাল একই instance:

চিত্র ৬: একটাই instance-এর জীবনচক্র

চিত্র ৬-এ SameInstanceReturned-এর loop-টা দেখো — এই loop-ই pattern-এর পুরো প্রতিশ্রুতি। প্রথম call-এর পরে আর কখনো নতুন কিছু তৈরি হয় না। আর NotCreated-এ ফেরার কোনো arrow নেই: একবার জন্ম নিলে program শেষ হওয়া পর্যন্ত বেঁচে থাকে। এটা মনে রেখো — পরে tests কেন এটা নিয়ে কষ্ট পায় সেটার কারণ এখানেই।

TypeScript-এ বাস্তব কোড: হেড স্যারের অফিস

চলো মতিঝিল মডেল স্কুল কোড করি। comment-গুলো পড়তে পড়তে এগোও।

// The one and only principal's office.
class PrincipalOffice {
  // Step 2: the private static field holding the single instance.
  // It is empty until someone first needs the office.
  private static instance: PrincipalOffice | null = null;
 
  // The official notice board (the office's state).
  private notices: string[] = [];
 
  // Step 1: PRIVATE constructor. Nobody outside can call `new`.
  private constructor() {
    console.log("🏫 Principal's office is being set up (runs only ONCE)...");
  }
 
  // Step 3 + 4: the public static door, with LAZY creation.
  public static getInstance(): PrincipalOffice {
    if (PrincipalOffice.instance === null) {
      // First visitor ever — set up the office now.
      PrincipalOffice.instance = new PrincipalOffice();
    }
    // Every visitor, first or later, gets the SAME office.
    return PrincipalOffice.instance;
  }
 
  public announce(notice: string): void {
    this.notices.push(notice);
    console.log(`📢 New notice: ${notice}`);
  }
 
  public readNotices(): string[] {
    return [...this.notices];
  }
}
 
// ---------- Client code: a normal school day ----------
 
// Anita Madam visits the office.
const officeForMaths = PrincipalOffice.getInstance();
officeForMaths.announce("Maths exam on Monday, 9 AM.");
 
// Vikram Sir visits "another" office... or does he?
const officeForSports = PrincipalOffice.getInstance();
officeForSports.announce("Annual sports day on 26 January.");
 
// Are these two different offices? Let's check!
console.log("Same office?", officeForMaths === officeForSports);
 
// Pinky reads the board — sees BOTH notices,
// because there is only ONE board in ONE office.
console.log("Notice board:", officeForSports.readNotices());
 
// And if someone naughty tries:  new PrincipalOffice();
// ❌ TypeScript error: Constructor of class 'PrincipalOffice' is private.

প্রত্যাশিত output:

🏫 Principal's office is being set up (runs only ONCE)...
📢 New notice: Maths exam on Monday, 9 AM.
📢 New notice: Annual sports day on 26 January.
Same office? true
Notice board: [ 'Maths exam on Monday, 9 AM.', 'Annual sports day on 26 January.' ]

Output-টা মনোযোগ দিয়ে পড়ো — এটা তিনটা প্রতিশ্রুতিই প্রমাণ করে:

  1. Setup message মাত্র একবারই print হয়েছে, যদিও getInstance() দুইবার call করা হয়েছে। এটাই lazy creation।
  2. officeForMaths === officeForSports হলো true — দুটো variable মেমোরিতে একই object-কে ধরে আছে।
  3. নাসরিন ম্যাডামের নোটিশ তারিক স্যার আর ফাতেমা দেখতে পাচ্ছে, কারণ সবাই একটাই নোটিশ বোর্ড share করছে।

একই মুহূর্তে দুজন এলে কী হবে?

JavaScript আর TypeScript সাধারণত একটাই thread-এ চলে, তাই দুটো call সত্যিকারের collision করতে পারে না — আমাদের সহজ if (instance === null) চেকটা সেখানে নিরাপদ।

কিন্তু C#, Java, বা C++-এ অনেক thread একসাথে চলে। এই race কল্পনা করো:

Thread A: checks instance — it is null      ─┐
Thread B: checks instance — it is also null ─┤ both passed the check!
Thread A: creates office #1                  │
Thread B: creates office #2                  │ ← TWO principals now! 😱

দুটো অফিস, দুটো নোটিশ বোর্ড — ঠিক যা Singleton ঠেকানোর প্রতিশ্রুতি দিয়েছিল। সমাধানগুলো হলো:

  • Eager creation: class load হওয়ার সময়ই instance তৈরি করো। সহজ আর নিরাপদ, কিন্তু laziness হারায়।
  • প্রতিটা call-এ lock: চেক করার আগে lock নাও। নিরাপদ, কিন্তু প্রতিটা call লকিংয়ের খরচ দেয়।
  • Double-checked locking: আগে lock ছাড়া চেক, শুধু তখনই lock যদি এখনো null থাকে, ভেতরে আবার চেক। দ্রুত — কিন্তু memory barrier ছাড়া ভুল করা বিখ্যাতভাবে সহজ।

কলেজের জন্য একটু বেশি — কেন double-checked locking বিশেষজ্ঞদেরও পুড়িয়েছে: বিপদটা logic-এ না, memory model-এ। instance = new PrincipalOffice() লাইনটা একটা atomic কাজ না। Compiler বা CPU এটাকে reorder করতে পারে: (১) memory বরাদ্দ, (২) pointer প্রকাশ, (৩) constructor চালাও। দ্বিতীয় একটা thread তখন non-null instance দেখতে পারে যার constructor এখনো শেষ হয়নি — আর অর্ধেক-তৈরি object ব্যবহার করতে পারে। Java এটা ঠিক করেছে Java 5 memory model-এ volatile দিয়ে; C#-এও volatile বা Interlocked দরকার। আধুনিক নিরাপদ উপায়: C#-এর Lazy<T>, Java-র class-holder idiom, Java-র enum singleton, Go-র sync.Once, Rust-এর OnceLock। শিক্ষাটা Singleton-এর বাইরেও যায়: এককালীন initialization কখনো নিজে হাতে তৈরি করো না; ভাষার নির্দিষ্ট tool ব্যবহার করো।

আধুনিক পরামর্শ এক লাইনে: এটা নিজে হাতে তৈরি করো না। এখন C#-এ যাই।

C#-এ একই ধারণা — Lazy<T> দিয়ে thread-safe

C# নিরাপদ lazy singleton-এর জন্য সুন্দর এক-লাইনার দেয়। Lazy<T> গ্যারান্টি দেয় factory মাত্র একবারই চলবে, এমনকি যদি একশোটা thread একই মুহূর্তে চায়।

public sealed class PrincipalOffice
{
    // Lazy<T> creates the instance on FIRST access,
    // and .NET guarantees thread safety for us. No locks to write!
    private static readonly Lazy<PrincipalOffice> _lazy =
        new(() => new PrincipalOffice());
 
    public static PrincipalOffice Instance => _lazy.Value;
 
    private readonly List<string> _notices = new();
 
    // Private constructor: outside code cannot use `new`.
    private PrincipalOffice()
    {
        Console.WriteLine("Principal's office set up (only once).");
    }
 
    public void Announce(string notice) => _notices.Add(notice);
 
    public IReadOnlyList<string> ReadNotices() => _notices;
}
 
// Usage — from anywhere, on any thread:
PrincipalOffice.Instance.Announce("PTM on Saturday at 10 AM.");
 
var a = PrincipalOffice.Instance;
var b = PrincipalOffice.Instance;
Console.WriteLine(ReferenceEquals(a, b)); // True — the same office

sealed কাউকে singleton subclass করতে বাধা দেয়। static readonly আর Lazy<T> মিলে পুরো thread-safety-র ধাঁধা এক লাইনে সামলায়। এটাই আধুনিক C# singleton-এর idiomatic উপায়।

সম্পূর্ণতার জন্য Python-ও দেখো। Python-এ সবচেয়ে সৎ singleton হলো সরাসরি একটা module — Python প্রতিটা module মাত্র একবার import করে cache করে, তাই module-level object-গুলো স্বাভাবিক singleton:

# water_tank.py — the module IS the singleton.
# Python runs this file only once, no matter how many files import it.
 
class _WaterTank:
    def __init__(self):
        self.level = 1000  # litres
 
    def use(self, litres: int) -> None:
        self.level -= litres
 
    def refill(self, litres: int) -> None:
        self.level += litres
 
# The one tank on the roof of Shanti Heights:
tank = _WaterTank()
 
# Any other file:
#   from water_tank import tank
#   tank.use(50)
# Everyone gets the same tank object — guaranteed by the import system.

Private constructor-এর কোনো ঝামেলা নেই। Import system নিজেই "একবার তৈরি, সবখানে share" দিয়ে দেয়।

বাস্তব software-এ কোথায় দেখা যায়

Singleton (আর singleton-এর মতো single instance) আসল system-এ সর্বত্র দেখা যায়:

  • Application configuration। একটা program settings file একবার parse করে সবখানে share করে। দুইবার parse করা সময় নষ্ট; দুটো দ্বিমত পোষণকারী config হবে bug।
  • Logger। Java-র Log4j বা Python-এর logging module প্রতিবার একই নামের জন্য একই logger object দেয় — log line মিশে যায় না।
  • Database connection pool। Pool database-এর সীমার জন্য সাইজ করা, তাই একটাই থাকতে হবে। প্রতি class-এ pool বানানো ক্লাসিক outage গল্প (চিত্র ৩ মনে করো)।
  • Spring Framework bean (Java)। এখানে মজার বিষয়: Spring-এ প্রতিটা bean-এর জন্য singleton ডিফল্ট scope — কিন্তু container সেটা পরিচালনা করে আর তোমার class-এ inject করে। তুমি কোথাও getInstance() না লিখেই single-instance পাও।
  • ASP.NET Core service। একই ধারণা: services.AddSingleton<MyService>() DI container-এ singleton lifetime দিয়ে class register করে।
  • Runtime object। Java-র Runtime.getRuntime() JDK-এর ভেতরে পাঠানো textbook Singleton।
  • Learning repository। iluwatar/java-design-patterns repo-তে পাঁচটা Java variant আছে।

এগুলোর মধ্যে কোনগুলো সত্যিই অনন্য? একটা সাধারণ backend-এ split-টা দেখতে এরকম:

চিত্র ৭: একটা সাধারণ backend-এ single-instance object সাধারণত কী হয়

শেষ ২০% টুকরোটা কষ্টের — যেটা কেউ শুধু সুবিধার জন্য singleton বানিয়েছে। পরের দুটো section ঠিক সেই টুকরো নিয়ে।

কখন ব্যবহার করবে, কখন না

পরিস্থিতিSingleton ব্যবহার করবে?
সত্যিকারের অনন্য resource (একটাই log file, একটাই parsed config, একটাই connection pool)✅ হয়তো — কিন্তু singleton lifetime সহ DI পছন্দ করো
ছোট script বা throwaway tool যেখানে DI অতিরিক্ত মনে হয়✅ হ্যাঁ — ঠিক আছে
বেশিরভাগ read-only, একবার-set করা value✅ গ্রহণযোগ্য — immutable singleton অনেক কম সমস্যা করে
"এই object পাস করতে করতে ক্লান্ত লাগছে"❌ না — অলসতা design-এর কারণ না
Class-টা mutable state ধরে রাখে যা অনেক module ব্যবহার করে❌ বিপজ্জনক
Unit test লিখবে এই class ব্যবহারকারী কোডে❌ এড়াও — test-এ সহজে fake swap করা যায় না
"হয়তো একদিন একের বেশি লাগবে"❌ না — পরে পরিবর্তন কষ্টকর
বড়, দীর্ঘমেয়াদী team project❌ singleton-scoped DI পছন্দ করো

একটু ভাবো — resource-টা প্রকৃতিগতভাবে কতটা অনন্য, আর এর state কতটা পরিবর্তিত হয়:

চিত্র ৮: কোথায় Singleton যুক্তিসঙ্গত

Shopping cart singleton হিসেবে (নিচে-বামে) মানে প্রতিটা user একই cart share করবে — ক্লাসিক বিপর্যয়। Parse করা read-only config (উপরে-ডানে) যতটা নিরাপদ singleton হতে পারে।

কেন অনেক সিনিয়র Singleton-কে anti-pattern বলেন

এবার সৎ কথা। যেকোনো অভিজ্ঞ ডেভেলপারকে Singleton সম্পর্কে জিজ্ঞেস করো, একটা সতর্ক ভ্রুকুটি দেখবে। Singleton একমাত্র GoF pattern যেটাকে নিয়মিতভাবে anti-pattern বলা হয় — একটা সমাধান যেটা সহায়ক মনে হয় কিন্তু নীরবে বড় সমস্যা তৈরি করে।

আসলে সমালোচনাটা "একটাই instance" ধারণার বিরুদ্ধে না। সেটা কখনো কখনো সত্যিই দরকার — একটা বাড়িতে একটাই পানির ট্যাংক সত্যিই সঠিক। সমালোচনাটা কৌশলের বিরুদ্ধে: একটা class নিজেই global static access দিচ্ছে।

চারটা বড় অভিযোগ, সহজ ভাষায়:

১. এটা fancy পোশাক পরা global state। দশকের পর দশক ধরে programmer-রা শিখেছেন global variable এড়াতে। একটা mutable singleton হুবহু একটাই global variable যেটা class-এর পোশাক পরেছে। Module A-তে পরিবর্তন করো, Module Z রহস্যজনকভাবে ভিন্ন আচরণ করে। এই "দূরত্বের ভৌতিক ক্রিয়া" debug করতে পুরো সন্ধ্যা চলে যায়। শান্তি হাইটস ভাষায়: যেকোনো ফ্ল্যাটের কেউ গোপনে ট্যাংকের outlet খুলতে পারলে কেউ ব্যাখ্যা করতে পারবে না কেন পানির চাপ কমে গেল।

২. Dependencies লুকিয়ে রাখে। এই function-টা দেখো:

function generateReportCard(student: Student): ReportCard {
  // ...looks like it depends only on `student`...
  const config = SchoolConfig.getInstance();  // surprise dependency!
  const logger = Logger.getInstance();        // another surprise!
  // ...
}

Signature বলছে "আমাকে একটা student দাও, আমি তোমাকে report card দেব।" কিন্তু এটা মিথ্যা বলছে। Function গোপনে দুটো singleton-এ পৌঁছাচ্ছে। Body-র প্রতিটা লাইন না পড়ে কেউ সত্যিকারের dependencies জানতে পারবে না।

৩. Testing কষ্টকর করে। ভালো unit test প্রতিটা test-এর জন্য একটা তাজা, বিচ্ছিন্ন দুনিয়া চায়। কিন্তু singleton হলো shared static state যেটা tests-এর মধ্যে টিকে থাকে — চিত্র ৬ মনে করো: NotCreated-এ ফেরার কোনো arrow নেই। Test 1 একটা নোটিশ পোস্ট করে; Test 2 অপ্রত্যাশিতভাবে সেটা দেখে। আরো খারাপ: fake দিয়ে বদলানো যায় না। OrderService যদি ভেতরে PaymentGateway.getInstance() call করে, তাহলে "order দাও" test করতে আসল payment gateway লাগে। আউচ।

৪. Thread-safety সহজেই ভুল হয়। নিজে হাতে লেখা lazy creation-এ race condition আছে। আর creation-এর পরেও singleton-এর নিজের mutable state synchronization চায় — পঞ্চাশটা thread একই নোটিশ বোর্ডে লিখলে এখনো গণ্ডগোল হবে।

🚨

সৎ সারসংক্ষেপ: ক্লাসিক Singleton = global mutable state + hidden dependencies + কঠিন testing। এটা জেনেশুনে ব্যবহার করো, ছোট মাত্রায়, ছোট program-এ। গুরুতর project-এ প্রথমে dependency injection ব্যবহার করো। কোনো সিনিয়র তোমার কোড review করে সর্বত্র getInstance() দেখলে প্রশ্ন করবে — আর সেগুলো ন্যায্য প্রশ্ন হবে।

তাহলে বিকল্প কী? সরলতম থেকে সবচেয়ে শক্তিশালী:

বিকল্পকীভাবে কাজ করেকখন মানানসই
Manual dependency injectionmain()-এ একটাই instance তৈরি করো, constructor-এর মাধ্যমে পাস করোছোট ও মাঝারি app; সবসময় নিরাপদ ডিফল্ট
Singleton lifetime সহ DI containerএকবার register করো (AddSingleton, Spring bean); container সবখানে inject করেTeam project, web backend — আধুনিক মান
Module-level instance (Python, JS/TS module)Module system নিজেই একটাই shared instance গ্যারান্টি দেয়Module-ভিত্তিক ভাষায় script ও app
সাদা function parameterযে কয়টা function-এর দরকার তাদের কাছে object-টা pass করোযখন মাত্র দুটো-তিনটা call site
Monostate / Borgঅনেক instance, একটাই shared stateবিরলভাবে; Singleton-এর একই নেতিবাচক দিক
Honest global constantএকটা exported immutable valueRead-only config-এর মতো data

আরো ভালো উপায়: dependency injection

এই দারুণ খবর — সুবিধাটা রেখে ("মাত্র একটাই logger, একটাই config, একটাই pool") প্রায় সব খরচ ছেড়ে দিতে পারবে। রেসিপিটাকে dependency injection (DI) বলে। নামটা ভয়ঙ্কর হলেও ব্যাপারটা সহজ:

  1. Program-এর শুরুতে একটাই instance তৈরি করো (main function — প্রায়ই composition root বলে)।
  2. সেটা যে class-গুলোর দরকার তাদের constructor-এর মাধ্যমে পাস করো।
// At startup — created ONCE, like appointing one principal:
const config = new SchoolConfig("settings.json");
const logger = new Logger(config);
const office = new PrincipalOffice(logger);
 
// Dependencies are now EXPLICIT and visible in constructors:
const examService = new ExamService(office, logger);
const sportsService = new SportsService(office);

এখনো মাত্র একটাই PrincipalOffice আছে — কিন্তু এখন:

  • ExamService প্রকাশ্যে বলে কী দরকার। Signature-এ কোনো মিথ্যা নেই।
  • Test-এ এক লাইনে FakePrincipalOffice দিতে পারবে। Testing সহজ।
  • কোনো global static state নেই।

School board (তোমার main function) জামাল স্যারকে একবার নিয়োগ দেয় আর প্রতিটা department-এর সাথে পরিচয় করিয়ে দেয়। কোনো শিক্ষক নিজেই হেড স্যার তৈরি করে না, তবুও সবাই একই মানুষের সাথে কাজ করে।

কলেজের জন্য একটু বেশি — "singleton" একটা lifetime হয়, pattern না: DI container (Spring, ASP.NET Core, Angular-এর injector, NestJS) "কতটা instance থাকবে" বিষয়টাকে configuration-এ পরিণত করে। ASP.NET Core-এ AddSingleton, AddScoped (প্রতিটা web request-এ একটা), আর AddTransient (প্রতিবার নতুন) হলো একই class-এর জন্য মাত্র তিনটা lifetime পছন্দ। Class নিজে সাদামাটাই থাকে — public constructor, explicit dependencies, সম্পূর্ণ testable। Container composition স্তরে uniqueness enforce করে। এই বিচ্ছেদটাই (class logic বনাম lifetime policy) প্রথম "কলেজের জন্য" অংশে উল্লেখ করা SRP লঙ্ঘনের সমাধান। একটা সতর্কতা আসল project থেকে: singleton-scoped service কখনো per-user বা per-request state রাখবে না, আর thread-safe হতে হবে, কারণ সব request সেটা share করে।

ছাত্রছাত্রীদের সাধারণ ভুলগুলো

⚠️

ভুল ১: Parameter পাস করা এড়াতে Singleton ব্যবহার। "getInstance() call করা object পাস করার চেয়ে সহজ" হলো সবচেয়ে সাধারণ ফাঁদ। স্বল্পমেয়াদী সুবিধা দীর্ঘমেয়াদী লুকানো coupling হয়ে যায়। কোনো class-এর যদি কিছু দরকার হয়, constructor-এ সেটা বলো।

⚠️

ভুল ২: Multi-threaded ভাষায় race condition ভুলে যাওয়া। সাদা "instance null হলে তৈরি করো" single-threaded JavaScript-এ ঠিক আছে, কিন্তু C#, Java, বা C++-এ দুটো thread উভয়ই চেক পার করে দুটো instance তৈরি করতে পারে। Lazy<T>, sync.Once, holder idiom, বা eager initialization ব্যবহার করো — মেমোরি থেকে double-checked locking কখনো নিজে লিখো না।

⚠️

ভুল ৩: Singleton আর static class এক মনে করা। Static class শুধু function-এর bundle — interface implement করতে পারে না, parameter হিসেবে pass করা যায় না, swap করা যায় না। Singleton হলো আসল object — এটা সব কাজ করতে পারে। "সুবিধার জন্য" সবকিছু static বানাচ্ছ দেখলে থামো আর ভাবো।

⚠️

ভুল ৪: Singleton-কে সব কিছুর আস্তাকুড় বানানো। Singleton সর্বত্র পৌঁছানো যায় বলে এটা ধীরে ধীরে config + cache + counter + helper function — সব একটাই বিশাল class-এ হয়ে যায়। এটা Single Responsibility Principle দুইবার লঙ্ঘন করে। Class ইতোমধ্যে দুটো কাজ করছে, তৃতীয় আর চতুর্থ কাজ যোগ করো না।

আত্মীয় pattern-এর সাথে তুলনা

Singleton creational pattern-গুলোর মধ্যে বসে, কিন্তু এর লক্ষ্য সেগুলোর বিপরীত — বাকিগুলো নমনীয়ভাবে অনেক object তৈরি করতে সাহায্য করে। Singleton তোমাকে একটার বেশি তৈরি করতে বাধা দেয়!

Patternকোন প্রশ্নের উত্তরকতটা instance?Singleton থেকে পার্থক্য
Singleton"মাত্র একটাই instance কীভাবে নিশ্চিত করব?"মাত্র একটা
Prototype"এটার মতো আরেকটা কীভাবে পাব?"যতটা copy চাওবিপরীত লক্ষ্য: Prototype বাড়ায়, Singleton সীমিত করে
Factory Method"কোন subclass ঠিক করবে কী তৈরি হবে?"অনেকপ্রতিটা call-এ নতুন object; চাইলে singleton return করতে পারে
Abstract Factory"সম্পর্কিত object-এর family কীভাবে তৈরি করব?"অনেকFactory object-টা নিজেই প্রায়ই Singleton হিসেবে implement হয়
Builder"ধাপে ধাপে complex object কীভাবে বানাব?"অনেকনতুন complex object তৈরি করে; uniqueness-এর সাথে সম্পর্ক নেই
Flyweight"Memory বাঁচাতে ছোট immutable object কীভাবে share করব?"অনেক shared, deduplicatedFlyweight অনেক immutable shared object; Singleton একটাই (প্রায়ই mutable) object

আরেকটা আত্মীয়ের কথা বলার মতো: Facade। একটা facade object (complex subsystem-এর সহজ সামনের দরজা) প্রায়ই Singleton বানানো হয়, কারণ প্রতিটা subsystem-এর একটাই সামনের দরজা যথেষ্ট।

পুরো বিষয়টা এক নজরে দেখতে — Singleton-এর একটা mind map:

চিত্র ৯: এক নজরে Singleton

দ্রুত revision বক্স

+--------------------------------------------------------------+
|                 SINGLETON PATTERN — REVISION                 |
+--------------------------------------------------------------+
| Idea       : Exactly ONE instance + one global access door   |
| Story      : One principal, one office, one notice board     |
|              (and one water tank for the whole building)     |
|                                                              |
| Recipe     : 1. private constructor  (lock the front door)   |
|              2. private static instance field                |
|              3. public static getInstance()                  |
|              4. lazy creation on first call                  |
|              5. thread-safety (Lazy<T> / sync.Once / eager)  |
|                                                              |
| Proof      : getInstance() twice -> SAME object (a === b)    |
| Lifecycle  : NotCreated -> Created -> SameInstanceReturned   |
|                                                              |
| Real world : config, logger, connection pool,                |
|              Spring beans (DI-managed!), Runtime.getRuntime  |
|                                                              |
| ⚠️ Honest warning — why seniors frown:                       |
|   - global mutable state in disguise                         |
|   - hidden dependencies (signatures lie)                     |
|   - tests cannot swap a fake; state leaks between tests      |
|   - thread-safe lazy init is easy to get wrong               |
|                                                              |
| Better way : dependency injection — create once at startup,  |
|              PASS it in; DI container singleton scope        |
+--------------------------------------------------------------+

অনুশীলন

তিনটা কাজ, সহজ থেকে চিন্তা উদ্দীপক — ফাতেমা-লেভেল থেকে জামাল-স্যার-লেভেল। পেছনে তাকানোর আগে চেষ্টা করো!

  1. পানির ট্যাংক। TypeScript-এ একটা WaterTank singleton লেখো। Private constructor, একটা getInstance() method, আর level শুরু হবে ১০০০ (লিটারে)। use(litres) আর refill(litres) method যোগ করো। তিনটা ভিন্ন "ফ্ল্যাট" থেকে (getInstance() দিয়ে পাওয়া তিনটা variable) কিছু পানি ব্যবহার করো, তারপর চতুর্থ variable থেকে level print করো। Combined effect দেখাতে হবে — প্রমাণ করে সবাই ছাদের একটাই ট্যাংক share করছে।

  2. ভাঙো, তারপর ঠিক করো। তোমার WaterTank নাও আর কল্পনা করো দুটো thread একই সময়ে getInstance() call করছে যখন instance null। কাগজে ধাপে ধাপে লেখো কীভাবে দুটো ট্যাংক তৈরি হতে পারে। তারপর Lazy<WaterTank> ব্যবহার করে C# version লেখো আর দুটো বাক্যে ব্যাখ্যা করো কেন race-টা অদৃশ্য হয়। Bonus কলেজ ছাত্রদের জন্য: ব্যাখ্যা করো কেন সঠিক memory barrier ছাড়া naive double-checked lock এখনো অর্ধেক-তৈরি ট্যাংক দিতে পারে।

  3. DI makeover (সবচেয়ে গুরুত্বপূর্ণ)। একটা BillingService লেখো যেটা প্রথমে ভেতরে WaterTank.getInstance() ব্যবহার করে। তারপর refactor করো: service-এর ভেতর থেকে getInstance() সরাও আর পরিবর্তে constructor-এর মাধ্যমে ট্যাংক নাও — new BillingService(tank)। শেষে একটা ছোট FakeWaterTank লেখো আর দেখাও কীভাবে refactored version আসল ট্যাংক না ছুঁয়েও BillingService test করতে দেয়। পার্থক্যটা অনুভব করো — সেই অনুভূতিটাই কারণ কেন সিনিয়ররা dependency injection পছন্দ করেন।

কাজ ৩ বোঝা গেলে অভিনন্দন — তুমি এখন শুধু Singleton pattern না, এর সবচেয়ে বড় সমালোচনা আর আধুনিক বিকল্পও বুঝে ফেলেছ। অনেক কর্মরত programmer এটা বলতে পারে না। শাবাশ, এগিয়ে যাও!

সচরাচর জিজ্ঞাসা

Singleton pattern কী — এক লাইনে বলো?
Singleton হলো একটা creational pattern যেটা নিশ্চিত করে পুরো program-এ একটা class-এর মাত্র একটাই instance থাকবে, আর সবাই একটাই পরিচিত পথে সেটায় পৌঁছাতে পারবে — সাধারণত একটা static getInstance() method-এর মাধ্যমে।
Singleton কীভাবে দ্বিতীয় object তৈরি হওয়া আটকায়?
Class তার constructor-কে private করে দেয়, ফলে বাইরের কোড new ডাকতে পারে না। Class নিজেই একটাই instance তৈরি করে আর একটা static method বা property-র মাধ্যমে সেটা সবাইকে দেয়।
অনেক ডেভেলপার কেন Singleton-কে anti-pattern বলেন?
কারণ এটা লুকানো global state তৈরি করে। Dependencies অদৃশ্য হয়ে যায়, unit test-এ singleton-কে সহজে fake দিয়ে বদলানো যায় না, আর tests-এর মধ্যে state চলে যায়। Dependency injection একই সুবিধা দেয় কিন্তু এই সমস্যাগুলো ছাড়াই।
Singleton-এ lazy initialization কী?
Lazy মানে instance প্রথমবার getInstance() ডাকার সময় তৈরি হয়, program শুরুতে না। এটা startup time বাঁচায়, কিন্তু multi-threaded program-এ creation-কে সুরক্ষিত রাখতে হবে যাতে দুটো thread দুটো instance তৈরি না করে।
Singleton আর static class কি একই জিনিস?
না। Static class শুধু function-এর একটা পাত্র — কখনো pass করা যায় না বা interface implement করা যায় না। Singleton হলো একটা আসল object, তাই এটা interface implement করতে পারে, parameter হিসেবে pass করা যায়, আর বদলানোও যায়।
বড় project-এ Singleton-এর বদলে কী ব্যবহার করা উচিত?
Program-এর শুরুতে একটাই instance তৈরি করে সেটাকে যে class-গুলোর দরকার তাদের মধ্যে দিয়ে দাও (dependency injection)। DI container-গুলো singleton lifetime দিয়ে class register করতে পারে, ফলে global static access ছাড়াই একটাই instance পাবে।

আরো দেখো

সম্পর্কিত পাঠ

Factory Method Pattern: Branch নিজেই ঠিক করুক কোন Vehicle

Factory Method design pattern শেখো রাহিমের টিফিন সার্ভিসের গল্পের মাধ্যমে — সহজ TypeScript আর C# code, diagram, real-world উদাহরণ, আর practice সহ।

আরও পড়ুন

Abstract Factory Pattern: এক অর্ডার, এক ম্যাচিং থালি

বাংলাদেশি বিয়ের ক্যাটারিং গল্পের মাধ্যমে Abstract Factory design pattern বুঝে নাও — সহজ TypeScript ও Python কোড, diagram, আর practice সহ।

আরও পড়ুন

Builder Pattern: একটু একটু করে বানাও, ওস্তাদ দর্জির মতো

ঢাকার একটা দর্জির দোকানের গল্পের মাধ্যমে Builder design pattern শেখো — fluent TypeScript ও C# কোড, telescoping constructor এর সমস্যা, আর practice টাস্কসহ।

আরও পড়ুন

Prototype Pattern: জিরো থেকে না বানিয়ে ফটোকপি করো

বিয়ের কার্ডের দোকানের গল্প দিয়ে Prototype design pattern শেখো — TypeScript আর Python-এর সহজ উদাহরণ, আর shallow vs deep copy-এর পরিষ্কার demo সহ।

আরও পড়ুন