تبلیغات

الگوی طراحی سینگلتون (Singleton) در جاوا

Singleton

الگوی طراحی سینگلتون (Singleton)، که معنای منحصر به فرد بودن را نیز شامل می‌شود، یکی از ساده‌ترین الگوهای طراحی استاندارد مورد استفاده در بین توسعه‌دهندگان زبان‌های برنامه‌نویسی مختلف است.  این الگو از دو اصل زیر تشکیل می‌شود:

  1. ایجاد نمونه از یک کلاس را محدود می‌کند تا همواره یک نمونه از آن در کل اپلیکیشن وجود داشته باشد.
  2. امکان دسترسی به نمونه‌ی ایجاد شده از کل اپلیکیشن را نیز فراهم می‌سازد.

کاربردهای زیادی در استفاده از این الگوی طراحی وجود دارد. از جمله logging، دیتابیس و …؛ و همچنین از این الگوی طراحی، در کنار الگوهای طراحی دیگری نیز از جمله بیلدر، فکتوری و … استفاده می‌شود.

سینگلتون در جاوا

در نگاه اول، این الگوی طراحی چیز ساده‌ای به نظر می‌آید؛ اما وقتی زمان پیاده‌سازی آن در جاوا فرار می‌رسد، نگرانی‌های پیاده‌سازی زیادی را به دنبال خود به همراه دارد! چراکه نحوه‌ی پیاده‌سازی این الگوی طراحی، همواره بین توسعه‌دهندگان بحث برانگیز بوده است. لذا ما در اینجا با روش‌های مختلف پیاده‌سازی‌ها آشنا خواهیم شد و بهترین آنها را انتخاب خواهیم کرد.

جالب است بدانید که از این الگوی طراحی در کلاس های اصلی جاوا از جمله java.lang.Runtime و java.awt.Desktop. نیز استفاده شده است.

برای پیاده‌سازی این الگو، ما رویکردهای مختلفی را داریم؛ اما تمامی آن‌ها در اصل‌های زیر یکسان هستند:

  • دسترسی متد سازنده باید private باشد؛ تا ایجاد نمونه از حارج کلاس (یا کلاس‌های دیگر) زا  محدود کند.
  • یک متغیر private static از همان کلاس و داخل کلاس باید تعریف شده باشد.
  • یک متد public static که نمونه‌ای از کلاس را برگرداند؛ و این یک نقطه‌ی دسترسی Global یا سراسری از کل اپلیکیشن به نمونه‌ی تعریف شده از کلاس خواهد بود.

در ادامه‌ی مقاله، ما روش‌های مختلفی را برای اجرای الگوی طراحی سینگلتون یاد گرفته و با نگرانی‌های(معضلات) طراحی هرکدام از آن‌ها آشنا می‌شویم.

  1. Eager initialization (آغازگر مشتاق)
  2. Static block initialization (آغازگر بلوک استاتیک)
  3. Lazy Initialization (آغازگر تنبل)
  4. Thread Safe
  5. Bill Pugh
  6. مشکل استفاه از رفلکشن حهت  تخریب الگوی سینگلتون
  7. Enum Singleton
  8. Serialization and Singleton

Eager initialization/آغازگر مشتاق

در روش آغازگر مشتاق، نمونه‌ی کلاس سینگلتون، در زمان بارگزاری کلاس ایجاد خواهد شد؛ و این ساده‌ترین روش برای ایجاد یک کلاس سینگلتون می‌باشد. اما مشکلی که دارد، این است که ممکن است هیچوقت از آن نمونه‌ی ایجاد شده استفاده نشود؛ در حالی که نمونه در زمان باگزاری کلاس در حافظه (مموری)، ایجاد می‌شود.

همانطور که می‌بینید، کلاس FuLLKade در همان جایی که تعریف شده است نیز مقداردهی می‌شود.

اگر کلاس سینگلتون شما از منابع زیادی استفاده نکند، این روش برای استفاده مناسب است؛ اما در اکثر سناریوها، کلاس سینگلتون معمولا برای دسترسی به منابعی مانند File System، کانکشن دیتابیس و … ساخته و طراحی می‌شود. بنابراین ما باید از ایجاد نمونه‌ی اولیه تا زمانی که متد getInstance فراخوانی نشده است، اجتناب کنیم. همچنین از اشکالات دیگر این روش این است که هیچ راه‌حلی را برای مدیریت Exception در اختیار ما قرار نداده است!

بنابراین اشکالات این روش:

  • ایجاد نمونه‌ای از کلاس در همان زمانی که کلاس در حافظه بارگزاری می‌شود و احتمالا از نمونه استفاده‌ای نشود.
  • نداشتن راه‌حل، برای مدیریت خطا (چرا که در همان ابتدا نمونه ایحاد شده است)

Static block initialization

این روش نیز همانند قبلی است با این تفاوت که مشکل دوم آن یعنی مدیریت خطا را با ایجاد نمونه‌ی کلاس در یک بلوک استاتیک رفع کرده می‌کند.

بنابراین هم روش قبل و هم این روش، هردویشان همچنان مشکل اول رادارند!

Lazy Initialization/آغازگر تنبل

در روش آغازگر تنبل، نمونه‌ی کلاس، در متد سراسری ایجاد خواهد شد.

همانطور که می‌بینید، نمونه داخل متد getInstance و در صورت null بودن ایجاد شده است؛ سپس این نمونه به بیرون متد برگردانده می‌شود.

البته این نوع پیاده‌سازی، در حالت Single Thread به خوبی کار می‌کند؛ اما زمانی که بخواهیم وارد مولتی تردینگ (Multi thread) شویم، ممکن است مشکلاتی را به وجود بیاورد. به این صورت که فرض کنید چند ترد (Thread) به صورت همزمان و داخل شرط if که برابر null است، قرار بگیرند! این باعث نابودی الگوی طراحی singleton خواهد شد! و تردهای مختلف، نمونه‌های جدا از همی را دریافت خواهند کرد! بنابراین در روش بعدی، این مشکل را نیز حل خواهیم کرد.

Thread Safe Singleton

ساده‌ترین راه برای امن کردن کلاس سینگلتون از شر برخورد Thread های مختلف، تعریف دستسرسی synchronized برای متد است. بنابراین تنها یک ترد خواهد توانست این متد را اجرا کند و بقیه‌ی تردها تا زمانی که ترد قبلی از متد خارج نشود، در صف انتظار قرار می‌گیرند.

حالا این پیاده‌سازی به خوبی کار خواهد کرد و همچنین امنیت تردها را نیز برایمان فراهم می‌کند؛ اما مشکلی که وجود دارد، باعث کاسته شدن کارایی (performance) می‌شود. چرا که از synchronized استفاده شده است. و اگرچه ما به آن فقط برای چند ترد اول که ممکن است نمونه‌های جدایی را ایجاد کنند، نیاز خواهیم داشت؛ ولی همواره روی متد اعمال شده باقی می‌ماند!

بنابراین برای حل این مشکل، از روش دیگر synchronized استفاده می‌کنیم:

همانطور که می‌بینید، در اینجا synchronized را بعد از بررسی null بودن نمونه و به صورت یک بلاک، داخل شرط نوشته‌ایم. لذا تردهای اضافه، بیرون آن منتظر خواهند ماند و سپس داخل آن هم دوباره null بودن را بررسی کرده‌ایم تا درصورت وجود تردهای اضافی در صف انتظار و داخل شرط اول، شرط دوم از ایجاد نمونه جلوگیری کند.

Bill Pugh Singleton

قبل از جاوا 5، مدل حافظه (مموری) جاوا، مشکلات زیادی داشت و رویکردهای فوق،برای رد شدن از سناریوهای خاصی که تردهای زیادی سعی در گرفتن نمونه از کلاس سینگلتون و به صورت همزمان داشتند، استفاده میشد. لذا بیل پگو آمد تا با روشی متفاوت، این کشکل را حل کند. این روش از یک کلاس استاتیک داخلی به صورت زیر استفاده می‌کند:

توجه داشته باشید، کلاس SingletonHelper که به صورت private static class و داخل کلاس اصلی (FuLLKade) تعریف شده است، شامل نمونه‌ای از کلاس  اصلی می‌باشد و این کلاس در زمان بارگزاری کلاس اصلی، بارگزاری نخواهد شد. و تنها وقتی بارگزاری می‌شود که متد getInstance فراخوانی شود.

این رویکرد به طور گسترده‌ای برای کلاس سینگلتون در نظر گرفته شده است و در آن نیازی به synchronization نخواهد بود. و من خودم از این رویکرد در پروژه‌های زیادی استفاده می‌کنم و فهم و پیاده‌سازی آن نیز بسیار ساده‌تر است.

مشکل استفاده از رفلکشن برای تخریب الگوی سینگلتون

قابلیت رفلکشن یا بازتاب در برنامه‌نویسی، می‌تواند برای نابود کردن تمامی ساختارهای سینگلتون بالا استفاده شود! چرا که با این قابلیت، می‌توان محدودیت ایجاد نمونه از کلاس را دور زد! به مثال زیر دقت کنید:

زمانی که کد بالا اجرا شود، متوجه خواهید شد که هش کد دو نمونه‌ی ایجاد شده از کلاس یکسان نیست! و این امر باعث نابودی الگوی طراحی سینگلتون ما خواهد شد!

Enum Singleton

برای غلبه بر مشکلی که بازتاب یا رفلکشن به وجود می‌آورد، آقای Joshua Bloch (که یکی از مهندسین نرم افزار دارای سابقه‌ی کار در شرکت Sun Microsystems و گوگل می‌باشد)، پیشنهاد کرده است که از Enum ها برای پیاده‌سازی الگوی طراحی سینگلتون استفاده شود. چرا که جاوا تضمین می‌کند هر کدام از مقادیر Enum ها، تنها یک بار ایجاد شوند. و از آنجایی که مقدار Enum ها نیز به صورت گلوبال قابل دسترس است، بنابراین سینگلتون نیز می‌باشد. اما اشکال تنها این است که نوع enum، قدری غیرقابل انعطاف می‌باشد؛ و برای مثال، به ما اجازه‌ی پیاده‌سازی روش lazy initialization را نمی‌دهد.

 

سریالیزیشن در سینگلتون

گاهی اوقات در سیستم‌های توزیع شده، ما نیاز داریم رابط‌های Serializable را در کلاس‌های سینگلتون خود پیاده کنیم؛ تا بتوانیم وضعیت آن‌هارا در فایل سیستم و در نقطه‌ی دیگری ذخیره کنیم. بنابراین در اینجا، یک کلاس کوچک سینگلتون برای پیاده‌سازی واسط Serializable در اختیار داریم:

مشکل با کلاس‌های سینگلتون serialized این است که هرگاه ما آن را deserialize کنیم، یک نمونه‌ی جدید از کلاس ایجاد خواهد کرد! اجازه دهید ببینیم:

خروجی کد بالا به صورت  زیر خواهد بود:

instanceOne hashCode=2011117821
instanceTwo hashCode=109647522

بنابراین این نیز الگوی طراحی سینگلتون را از بین می‌برد؛ و برای غلبه بر این سناریو؛ همه چیزی که ما نیاز داریم تا انجام دهیم، مجهز کردن پیاده‌سازی متد readResolve() با Override کردن آن می‌باشد.

بعد از اینکار، مشکل شما حل شده و خواهید دید که هش کد هردو مورد یکسان است.


امیدوارم این مقاله به درک الگوی طراحی سینگلتون در جاوا برای شما کمک کرده باشد. موفق باشید.

تبلیغات
0
کانال تلگرام فول کده
تبلیغات

درباره نویسنده

هادی اکبرزاده

[ مدیر فول کده ]

علاقه‌مند به اشتراک گذاری اطلاعات در هر زمینه‌ای / برنامه‌نویس

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

نظرات ثبت شده بدون دیدگاه