در این مقاله قصد دارم الگوی سینگلتون (Singleton Pattern) را بررسی کنم. Singleton Pattern یک Software Design Pattern است که اجازه ساخت تنها یک instance از class مورد نظر را میدهد.
این الگوی طراحی نرمافزار در مواقعی که حداکثر به یک instance نیاز است استفاده می شود، برای توضیح بهتر از چند مثال واقعی استفاده میکنم. مثلا برنامهای داریم که با یک printer کار میکند و تنها میتوان یک instance از connection با printer داشت، در حالی که ممکن است به روشٰهای مختلف و از بخشهای متفاوتی از برنامه، printer صدا زده شود و نیاز باشد که connection در دسترس قرار گیرد. در اینجا ما میتوانیم از Singleton Pattern استفاده کنیم که تضمین میکند فقط در بار اول یک instance ساخته میشود و دفعات بعدی از همان استفاده میشود. مثال دیگر میتواند برای log زدن در یک برنامه باشد. از هر جای برنامه ممکن هست log زده شود و هر بخشی ممکن است با logger ارتباط برقرار کند، اما اگر همه log ها قرار باشد در یک فایل ذخیره شود و به طور کلی یک logger برای کل برنامه نیاز داشته باشیم، باید این محدودیت در تعداد ساخت instance ایجاد شود وگرنه با هر call، یک instance جدید ساخته میشود و به ازای هر log یک object و فایل جدیدی برای ذخیرهسازی log داریم که اصلا وضعیت مطلوبی نیست و در نتیجه از Singleton Pattern استفاده میشود تا یک instance برای log زدن داشته باشیم و در تمام برنامه از آن استفاده شود. ممکن هست که در ارتباط با دیتابیس هم نیاز به Singleton Pattern باشد تا به ازای هر instance یک connection pool جدید ساخته نشود یا مجبور نشویم که برای هر query از connection جدیدی استفاده کنیم.
الگوی سینگلتون را میتوان با زبانهای مختلف پیاده سازی کرد که در این مقاله ما پیاده سازی آن در python را بررسی میکنیم. در خود python هم میتوان به روشهای مختلفی کد را پیاده سازی کرد و به عنوان decorator یا metaclass از آن بهره برد که ما استفاده از metaclass را بررسی می کنیم.
class Singleton(type):
def __init__(cls, what, bases=None, dict_=None):
super().__init__(what, bases, dict_)
cls._instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
همانطورکه که میدانید همه چیز در پایتون object است و حتی خود کلاس هم object از نوع type است که در نهایت خود type هم object از کلاس object است. در کد بالا، در قسمت __init__ اطمینان حاصل میکنیم که instance از کلاس مورد نظر وجود نداشته باشد.
در قسمت __call__ بررسی می کنیم که اگر class برای بار اول صدا زده می شود، instance از آن ساخته شود، یعنی در وضعیتی که instance از قبل وجود ندارد، وگرنه از instance موجود استفاده میکند.
برای استفاده از کد بالا به صورت metaclass می توانیم به این طریق عمل کنیم:
class ExampleClass(metaclass=Singleton):
pass
توجه به این نکته ضروری است که instance باید immutable باشد، یعنی بر اساس شرایط مختلف و نحوه استفاده، وضعیت و عملکرد آن تغییر نکند، وگرنه Singleton بودن آن ممکن است مشکلات بسیاری ایجاد میکند، همان طور که global variable ممکن است مشکل ایجاد کند.
در پایان باید اشاره کنم که مخالفت هایی با Singleton Pattern وجود دارد و آن را Anti Pattern میدانند، چرا که به نوعی شبیه به global variable است و instance تعریف شده global میشود، در نتیجه مگر در موارد خاص و مشخص که واضح است باید از این الگو استفاده کرد، استفاده از آن به صورت عمومی و گسترده توصیه نمیشود چه بسا که در بسیاری از موارد، خود کتابخانهها و برنامههای مختلف در صورت نیاز، این موضوع را در نظر گرفتهاند و نیاز به پیادهسازی از سمت توسعه دهندههای دیگر ندارد.