- 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 میتواند انعطافپذیری و تستپذیری بهتری ارائه دهد.