- Published on
الگوی طراحی Singleton (تکنمونه)
- نویسندگان

- نام
- هومن امینی
- توییتر
- @HoomanAmini
الگوی Singleton: راهکاری برای ایجاد یک نمونهی یکتا
مقدمه
در دنیای برنامهنویسی شیءگرا، مواقعی وجود دارد که نیاز داریم از یک کلاس فقط یک نمونه در سراسر برنامه داشته باشیم. اینجاست که الگوی طراحی Singleton وارد عمل میشود. Singleton تضمین میکند که از یک کلاس خاص، فقط یک و تنها یک نمونه وجود داشته باشد و در عین حال، یک نقطهی دسترسی جهانی برای آن فراهم میکند.
چرا Singleton؟
برخی از مواردی که Singleton در آنها به کار میرود:
- مدیریت منابع مشترک: مانند اتصال به پایگاه داده، کش، یا فایلهای پیکربندی.
- کنترل دسترسی به منابع: مانند مدیریت جلسات (Sessions) در برنامههای تحت وب.
- ساختارهای سیستمی: مانند مدیریت چاپگر یا تنظیمات سیستمعامل.
مشکلات متغیرهای سراسری
برخی توسعهدهندگان ممکن است متغیرهای سراسری (Global Variables) را به عنوان جایگزین Singleton پیشنهاد دهند، اما این روش مشکلاتی دارد:
- کنترل ضعیف روی مقداردهی اولیه: ممکن است نمونه زودتر از نیاز ایجاد شود.
- وابستگی بالا: باعث کاهش انعطافپذیری و افزایش وابستگیهای متقابل در کد میشود.
- مشکل در چندنخی (Multithreading): ممکن است چندین نمونه بهطور همزمان ایجاد شوند.
پیادهسازی الگوی Singleton
پیادهسازی کلاسیک
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {} // جلوگیری از نمونهسازی مستقیم
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
مشکل: این پیادهسازی در برنامههای چندنخی (Multithreading) مشکل دارد، زیرا ممکن است دو Thread همزمان getInstance() را اجرا کرده و دو نمونه مختلف ایجاد کنند.
راهکار: همگامسازی (Synchronization)
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
- مزیت: جلوگیری از مشکل چندنخی
- عیب: کندی عملکرد به دلیل استفاده از
synchronizedدر هر بار دسترسی
راهحل بهتر: قفلگذاری دو مرحلهای (Double-Checked Locking)
public class Singleton {
private static volatile Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- مزیت: کارایی بالا و ایمنی در چندنخی
- نقش
volatile: از کش شدن مقدار در CPU جلوگیری میکند.
بهترین روش در جاوا: استفاده از Enum
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Singleton is working");
}
}
- مزیت: مدیریت خودکار چندنخی، جلوگیری از Serialization و Reflection
- استفاده:
Singleton.INSTANCE.doSomething();
چرا enum برای Singleton؟
استفاده از enum در جاوا یک روش ایمن، ساده و بهینه برای پیادهسازی Singleton است. دلایل این برتری عبارتند از:
- تضمین یکتا بودن نمونه: جاوا تضمین میکند که فقط یک نمونه از
INSTANCEایجاد شود. - امنیت در برابر چندنخی (Thread-Safety): نیازی به
synchronizedیاvolatileنیست، زیرا مدیریت نمونهسازی بهطور خودکار توسط جاوا انجام میشود. - محافظت در برابر Serialization: در روشهای کلاسیک، اگر یک Singleton قابل سریالسازی (Serializable) باشد، میتوان با
ObjectInputStreamنمونههای جدیدی ایجاد کرد. اماenumبهصورت پیشفرض در برابر این مشکل مقاوم است. - محافظت در برابر Reflection: در روشهای سنتی، با استفاده از Reflection میتوان متد
private constructorرا دور زد و نمونههای جدید ایجاد کرد. اماenumاین مشکل را ندارد.
با این حال، enum دارای محدودیتهایی است:
- عدم امکان Lazy Initialization: نمونه
INSTANCEاز ابتدا ایجاد میشود، حتی اگر هرگز استفاده نشود. - عدم قابلیت ارثبری: چون
enumازjava.lang.Enumارثبری میکند، امکان ارثبری از کلاسهای دیگر را ندارد.
مقایسه روشهای مختلف
| روش | کارایی | امنیت در چندنخی | انعطافپذیری |
|---|---|---|---|
| کلاسیک | متوسط | ضعیف | خوب |
| همگامسازی | کم | قوی | متوسط |
| قفلگذاری دو مرحلهای | بالا | قوی | متوسط |
Enum | بسیار بالا | بسیار قوی | کم |
آیا Singleton همیشه بهترین گزینه است؟
هرچند Singleton در بسیاری از موارد مفید است، اما همیشه بهترین گزینه نیست. در برخی سناریوها استفاده از Dependency Injection (DI) راهکار بهتری است:
- افزایش تستپذیری (Testability): Singletonها تست را دشوارتر میکنند، در حالی که DI این مشکل را حل میکند.
- کاهش وابستگیهای سختکد شده: Singleton باعث ایجاد وابستگیهای سراسری میشود که قابلیت تغییر را کاهش میدهد، اما DI انعطاف بیشتری فراهم میکند.
- مدیریت بهتر چرخهی حیات اشیا: Singleton همیشه در حافظه باقی میماند، اما با DI میتوان چرخهی حیات را بهتر مدیریت کرد.
بنابراین، در برنامههایی که مقیاسپذیری و تستپذیری بالا مورد نیاز است، بهتر است به جای Singleton از مدیریت وابستگیها در فریمورکهایی مانند Spring یا Guice استفاده شود.
نتیجهگیری
الگوی Singleton یک راهکار مناسب برای مدیریت منابع محدود و دادههای سراسری در برنامهها است. پیادهسازی صحیح این الگو از ایجاد نمونههای غیرضروری جلوگیری کرده و کارایی سیستم را بهبود میبخشد. روش Enum به عنوان بهترین گزینه در جاوا توصیه میشود، اما بسته به نیاز پروژه میتوان از روشهای دیگر نیز استفاده کرد. همچنین در برخی موارد، استفاده از Dependency Injection به جای Singleton میتواند انعطافپذیری و تستپذیری بهتری ارائه دهد.