الگوی فکتوری یک الگوی طراحی سازنده (Creational Design Pattern) است که یک رابط برای ایجاد اشیا فراهم میکند بدون اینکه کلاس دقیق آن اشیا را مشخص کند. به جای فراخوانی مستقیم سازنده یک کلاس، شما یک متد فکتوری را صدا میزنید که تصمیم میگیرد کدام کلاس نمونهسازی شود. این الگو راهحلی اثباتشده برای مشکلات رایج در توسعه نرمافزار، به ویژه زمانی است که با کد تکراری برای ایجاد اشیا یا مدیریت انواع مختلف اشیا دستوپنجه نرم میکنید. برای درک بهتر، آن را مانند سفارش غذا در یک رستوران در نظر بگیرید: شما به آشپزخانه نمیروید تا غذای خود را آماده کنید؛ بلکه به پیشخدمت میگویید چه میخواهید و آشپزخانه (که نقش فکتوری را ایفا میکند) آن را برای شما ایجاد میکند. شما وعده غذایی خود را بدون نگرانی در مورد دستور پخت یا فرآیند پخت دریافت میکنید.
الگوی فکتوری زمانی مفید است که با چندین کلاس مرتبط سروکار دارید که یک رابط مشترک دارند اما پیادهسازیهای متفاوتی دارند (مانند پردازشگرهای پرداخت یا اتصالات پایگاه داده). این الگو به شما کمک میکند تا تصمیم بگیرید کدام کلاس باید در زمان اجرا (Runtime) نمونهسازی شود. این تصمیم میتواند بر اساس ورودی کاربر، پیکربندی برنامه یا سایر شرایط زمان اجرا باشد. از دیگر موارد استفاده کلیدی این الگو، زمانی است که منطق ایجاد شی پیچیده است و شما میخواهید آن را کپسوله کنید. این کار نه تنها کد شما را تمیزتر و قابل نگهداریتر میکند، بلکه تستپذیری را نیز افزایش میدهد، زیرا منطق ایجاد در یک نقطه متمرکز شده است. با این حال، نباید از این الگو زمانی استفاده کنید که تنها یک یا دو کلاس ساده دارید، ایجاد شی straightforward است و منطق خاصی ندارد، یا زمانی که افزودن این لایه انتزاعی، درک کد را سختتر میکند.
پیادهسازی الگوی فکتوری میتواند با یک مثال ساده آغاز شود. فرض کنید یک سیستم اطلاعرسانی میسازید که میتواند پیامها را از طریق ایمیل، اساماس یا نوتیفیکیشن push ارسال کند. بدون فکتوری، مجبورید در سراسر برنامه خود کدی شبیه به این بنویسید که به سرعت نامرتب میشود. با استفاده از فکتوری، شما یک کلاس مانند NotificationFactory ایجاد میکنید که یک متد ایستا به نام create_notifier دارد. این متد یک پارامتر رشتهای میگیرد و شی ناتیفایر مناسب را برمیگرداند. این کار منطق ایجاد شی را در یک نقطه متمرکز میکند. برای پاکتر کردن کد، به جای استفاده از زنجیره if-elif میتوان از یک دیکشنری استفاده کرد که نام نوع ناتیفایر را به آبجکت کلاس مربوطه نگاشت میکند. این رویکرد افزودن انواع جدید را بسیار آسانتر میکند.
در دنیای واقعی، اشیا اغلب به پارامترهای پیکربندی نیاز دارند. میتوان فکتوری را به گونهای گسترش داد که پارامترهای اولیه را بپذیرد. برای مثال، یک مولد سند که قالبهای فایل مختلف با تنظیمات سفارشی تولید میکند. در این حالت، متد فکتوری علاوه بر نوع سند، پارامترهای عنوان و نویسنده را نیز میپذیرد و آنها را به سازنده کلاس مورد نظر ارسال میکند. برای قویتر کردن فکتوری و تضمین یک رابط مشترک، میتوان از کلاسهای پایه انتزاعی (Abstract Base Classes یا ABC) در پایتون استفاده کرد. این کلاسها یک blueprint تعریف میکنند که تمام زیرکلاسهای concrete باید متدهای آن را پیادهسازی کنند و از این طریق کد را قابل پیشبینیتر و ایمنتر میسازند.
یک نمونه کاربردی و واقعی از الگوی فکتوری، ایجاد اتصالات مختلف به پایگاه داده بر اساس پیکربندی است. در این سناریو، چندین کلاس اتصال به پایگاهداده (مانند PostgreSQLConnection، MySQLConnection) داریم که هر کدام رابط یکسانی دارند اما پیادهسازیهای متفاوتی برای برقراری ارتباط با موتورهای پایگاه داده مختلف ارائه میدهند. فکتوری میتواند دو متد ایجاد داشته باشد: یک متد برای پارامترهای مستقیم و متد دیگر برای دیکشنریهای پیکربندی. متدی که پیکربندی را از یک فایل یا متغیرهای محیطی میخواند، به ویژه مفید است زیرا به برنامه اجازه میدهد بدون تغییر در کد اصلی، به راحتی بین پایگاههای داده مختلف جابجا شود. کافی است پیکربندی را تغییر دهید و فکتوری به طور خودکار شی اتصال مناسب را ایجاد میکند.
الگوی فکتوری یک ابزار قدرتمند برای مدیریت ایجاد اشیا در پایتون است. این الگو با متمرکز کردن منطق ایجاد و جدا کردن کد شما از پیادهسازیهای کلاس خاص، به شما کمک میکند کد تمیزتر و قابل نگهداریتری بنویسید. نکته کلیدی این است: هر زمان که خود را در حال نوشتن کد تکراری برای ایجاد شی یافتید یا نیاز داشتید تصمیم بگیرید کدام کلاس در زمان اجرا نمونهسازی شود، استفاده از الگوی فکتوری را در نظر بگیرید. ساده شروع کنید و تنها در صورت لزوم پیچیدگی اضافه کنید. اغلب اوقات، یک فکتوری ساده مبتنی بر دیکشنری برای اکثر برنامهها کافی است.
فرض کنید در حال ساخت یک سیستم اطلاعرسانی هستید که میتواند پیامها را از طریق ایمیل، پیامک (SMS) یا نوتیفیکیشنهای Push ارسال کند. بدون استفاده از الگوی فکتوری، ممکن است در سرتاسر برنامه خود با کدی شبیه به این مواجه شوید که به صورت مستقیم و بسته به شرایط، شیء مورد نظر را ایجاد میکند. این رویکرد نه تنها منجر به تکرار کد میشود، بلکه وابستگی شدیدی بین کد اصلی و کلاسهای خاص ایجاد میکند، که در بلندمدت نگهداری و تست کد را دشوار میسازد.
الگوی فکتوری با ارائه یک رابط یکپارچه برای ایجاد اشیاء، این مشکل را حل میکند. در مثال سیستم نوتیفیکیشن، ابتدا کلاسهای مختلف نوتیفایر را تعریف میکنیم که یک رابط مشترک (مانند یک متد `send`) دارند. برای مثال، کلاسهای `EmailNotifier`، `SMSNotifier` و `PushNotifier` هر یک پیادهسازی خاص خود را برای ارسال اطلاعیه دارند. سپس یک کلاس فکتوری به نام `NotificationFactory` ایجاد میکنیم. این کلاس دارای یک متد استاتیک به نام `create_notifier` است که نقش کارخانه را ایفا میکند. این متد یک پارامتر رشتهای (مثلاً 'email', 'sms', 'push') دریافت میکند و بر اساس آن، شیء نمونهای از نوتیفایر مرتبط را برمیگرداند. دکوراتور `@staticmethod` به ما این امکان را میدهد که بدون نیاز به نمونهسازی از خود کلاس فکتوری، مستقیماً از این متد استفاده کنیم.
در اولین قدم پیادهسازی، فکتوری ممکن است از یک زنجیره `if-elif` برای انتخاب کلاس مناسب استفاده کند. به این صورت که مقدار ورودی را بررسی کرده و در صورت مطابقت، کلاس مربوطه را نمونهسازی میکند. حالا به جای فراخوانی مستقیم سازنده کلاسها در سراسر برنامه، تنها کافی است `NotificationFactory.create_notifier('sms')` را فراخوانی کنیم. این کار منطق ایجاد شیء را در یک نقطه متمرکز میکند. اگر در آینده نیاز به افزودن یک نوتیفایر جدید (مثلاً `SlackNotifier`) داشته باشیم، تنها کافی است یک شرط جدید به فکتوری اضافه کنیم و نیازی به تغییر در دهها نقطه از کد برنامه نیست. این امر اصل "تمرکز مسئولیت" را به خوبی رعایت میکند.
استفاده از زنجیره شرطی برای فکتوریهایی که تعداد کلاسهای زیادی دارند، میتواند باعث شلوغی و طولانی شدن کد شود. یک راهحل بسیار تمیزتر و پایتونیتر، استفاده از یک دیکشنری برای نگاشت نام نوع به کلاس مربوطه است. در این روش، یک دیکشنری درون کلاس فکتوری تعریف میشود که کلیدهای آن نام انواع نوتیفایر (مانند 'email') و مقادیر آن، ارجاع به خود کلاسها (مانند `EmailNotifier`) هستند. سپس متد `create_notifier` با استفاده از متد `get` دیکشنری، کلاس مربوطه را برمیگرداند. اگر کلید در دیکشنری وجود نداشته باشد، میتوان `None` برگرداند یا یک خطای مناسب ایجاد کرد. در نهایت، با فراخوانی کلاس برگشتی (مانند `notifier_class()`) شیء نمونهسازی میشود. این روش، کد فکتوری را کوتاهتر، خواناتر و انعطافپذیرتر میسازد، زیرا افزودن یک نوتیفایر جدید تنها مستلزم افزودن یک جفت کلید-مقدار جدید به دیکشنری است.
پیادهسازی فکتوری برای سیستم نوتیفیکیشن، مزایای متعددی به همراه دارد. اولاً، وابستگی کد اصلی به کلاسهای concreate (عیان) را از بین میبرد و آن را به یک واسط انتزاعی (فکتوری) وابسته میکند. این امر اصل وارونگی وابستگی (Dependency Inversion Principle) را تقویت میکند. ثانیاً، با متمرکز کردن منطق ایجاد، تغییرات آینده (مانند اضافه کردن انواع جدید یا تغییر منطق نمونهسازی) را بسیار ساده میکند. ثالثاً، تستپذیری (Testability) کد را افزایش میدهد، زیرا به راحتی میتوان یک فکتوری Mock ایجاد کرد تا اشیاء آزمایشی را بازگرداند. این مثال ساده، هسته اصلی و قدرت الگوی فکتوری را در نوشتن کدهای تمیز، قابل نگهداری و مقیاسپذیر نشان میدهد.
پیادهسازی اولیه الگوی فکتوری با استفاده از زنجیره if-elif-else، اگرچه کاربردی است، اما با افزایش تعداد کلاسها به سرعت پیچیده و غیرقابل مدیریت میشود. تصور کنید سیستمی داشته باشید که باید بین دهها نوع نوتیفایر یا پردازشگر پرداخت تصمیمگیری کند. در چنین سناریوهایی، بلوک شرطی بسیار طولانی شده و نگهداری آن دشوار میگردد. هر بار که کلاس جدیدی اضافه میشود، باید یک شرط elif جدید به این زنجیره اضافه کنید که احتمال خطا را افزایش میدهد. اینجاست که نیاز به بهینهسازی و استفاده از ساختارهای دادهای پویاتر مانند دیکشنری احساس میشود.
یک راهحل بسیار تمیزتر و پایتونیک، جایگزینی زنجیره if-elif با یک دیکشنری است. در این روش، یک دیکشنری ایجاد میکنیم که نام هر نوع شیء (به صورت رشته) را به کلاس مربوطه نگاشت میدهد. برای مثال، به جای بررسی شرطی برای 'email'، 'sms' و 'push'، یک دیکشنری مانند {'email': EmailNotifier, 'sms': SMSNotifier} تعریف میشود. مزیت اصلی این روش، سادگی افزودن کلاسهای جدید است. شما فقط نیاز دارید یک جفت کلید-مقدار جدید به این دیکشنری اضافه کنید، بدون آنکه منطق اصلی فکتوری را تغییر دهید. این امر کد را بسیار ماژولار و مطابق با اصل Open/Closed (باز برای گسترش، بسته برای تغییر) میسازد. علاوه بر این، میتوان از متد get() دیکشنری برای مدیریت حالتهای پیشفرض یا خطا به صورت ظریف استفاده کرد.
اشیاء در دنیای واقعی اغلب برای مقداردهی اولیه به پارامترهای خاصی نیاز دارند. یک نوتیفیکیشن ایمیل ممکن است به آدرس فرستنده و گیرنده نیاز داشته باشد، یا یک اتصال پایگاهداده به آدرس سرور و اعتبارنامه. الگوی فکتوری به راحتی میتواند این نیاز را برطرف کند. با گسترش متد فکتوری، میتوانیم آن را طوری طراحی کنیم که علاوه بر نوع شیء، پارامترهای مورد نیاز برای ساخت آن شیء را نیز بپذیرد. این پارامترها سپس به سازنده کلاس مناسب منتقل میشوند. این قابلیت به فکتوری این قدرت را میدهد که نه تنها تصمیمگیری کند کدام کلاس نمونهسازی شود، بلکه آن را با تمام پیکربندی لازم نیز آماده کند. این امر وابستگی کد کلاینت به جزئیات ساخت شیء را بیشتر کاهش داده و ایجاد اشیاء پیچیده را ساده مینماید.
برای درک بهتر این دو مفهوم (دیکشنری و پارامترها)، یک سیستم تولید سند را در نظر بگیرید که میتواند اسناد را در قالبهای مختلفی مانند PDF، Word یا HTML تولید کند. هر سند به پارامترهای خاصی مانند عنوان و نویسنده نیاز دارد. یک فکتوری بهینهشده برای این سناریو میتواند از یک دیکشنری برای نگاشت نام قالب (مثلاً 'pdf') به کلاس مربوطه (مثلاً PDFDocument) استفاده کند. متد create_document این فکتوری، سه پارامتر میگیرد: doc_type (نوع سند)، title (عنوان) و author (نویسنده). فکتوری با استفاده از دیکشنری، کلاس مناسب را پیدا کرده و سپس آن را با ارسال پارامترهای title و author به سازندهاش، نمونهسازی میکند. نتیجه، یک شیء سند کاملاً پیکربندیشده است که آماده استفاده میباشد. این روش، تمرکز و انعطافپذیری بالایی را به ارمغان میآورد.
بهکارگیری این تکنیکهای بهینهسازی—استفاده از دیکشنری به جای شرطهای طولانی و پشتیبانی از پارامترها—منجر به ایجاد فکتوریهایی میشود که دارای مزایای کلیدی زیر هستند:
این بهینهسازیها الگوی فکتوری را از یک مفهوم ساده به یک ابزار قدرتمند و صنعتی برای مدیریت ایجاد اشیاء در برنامههای پیچیده تبدیل میکنند.
کلاسهای پایه انتزاعی یا Abstract Base Classes (ABC) در پایتون، یک ابزار قدرتمند برای ایجاد یک ساختار رابط (Interface) مشترک و اجباری برای کلاسهای مرتبط هستند. هدف اصلی آنها این است که به عنوان یک الگو یا طرح کلی عمل کنند و تضمین کنند که تمام کلاسهایی که از این پایه انتزاعی ارثبری میکنند، متدهای خاصی را پیادهسازی کردهاند. این مکانیسم، پایهای مستحکم برای پیادهسازی الگوی فکتوری فراهم میکند، زیرا اطمینان حاصل میکند که هر شیء ایجاد شده توسط فکتوری، دارای مجموعهای یکسان و قابل پیشبینی از متدها و رفتارها خواهد بود. در واقع، ABCها قرارداد واضحی برای کد شما تعریف میکنند.
برای درک بهتر، یک سیستم ساده پردازش پرداخت را در نظر بگیرید. در این سناریو، ما میخواهیم فکتوری داشته باشیم که بتواند اشیایی برای پردازشگرهای مختلف پرداخت (مانند کارت اعتباری، پیپال و غیره) ایجاد کند. نکته کلیدی این است که تمام این پردازشگرها باید دارای متدهای یکسانی مانند `process_payment` و `refund` باشند. اینجاست که ABC وارد عمل میشود. یک کلاس پایه انتزاعی به نام `PaymentProcessor` ایجاد میکنیم که این متدها را به عنوان متدهای انتزاعی (Abstract) تعریف میکند. استفاده از دکوراتور `@abstractmethod` به پایتون میگوید که این متدها باید توسط هر کلاس فرزندی بازنویسی (Override) شوند. خود کلاس `PaymentProcessor` قابل نمونهسازی نیست و تنها نقش یک طرح را ایفا میکند.
ادغام کلاسهای پایه انتزاعی با الگوی فکتوری چندین مزیت مهم به همراه دارد که کد شما را قابل اطمینانتر و حرفهایتر میسازد. اولین و مهمترین مزیت، تضمین صحت ساختاری است. اگر یکی از کلاسهای پردازشگر (مثل `CreditCardProcessor`) یکی از متدهای انتزاعی را پیادهسازی نکند، پایتون به طور خودکار در زمان تعریف کلاس خطا میدهد و از بروز خطاهای زمان اجرا جلوگیری میکند. دومین مزیت، ایجاد واسطی یکپارچه است. این کار کد شما را قابل پیشبینیتر و تستپذیرتر میکند، زیرا شما مطمئن هستید که هر شیء برگردانده شده از فکتوری، بدون در نظر گرفتن کلاس خاص آن، دارای متدهای یکسانی است. در نهایت، این روش نگهداری و توسعه کد را آسانتر میکند، زیرا اضافه کردن یک پردازشگر جدید تنها با ایجاد یک کلاس فرزند جدید که متدهای انتزاعی را پیادهسازی میکند، ممکن میشود و فکتوری بدون نیاز به تغییر در منطق اصلی، به سادگی آن را خواهد پذیرفت.
در این نمونه کد، نحوه استفاده عملی از ABCها برای ایجاد یک فکتوری قابل اطمینان نشان داده میشود. ابتدا کلاس پایه انتزاعی `PaymentProcessor` را با استفاده از ماژول `abc` تعریف میکنیم. سپس کلاسهای concrete یا واقعی مانند `CreditCardProcessor` و `PayPalProcessor` را ایجاد میکنیم که از این کلاس پایه ارثبری کرده و متدهای انتزاعی را پیادهسازی میکنند. در نهایت، یک فکتوری ساده (که میتواند بر پایه دیکشنری باشد) ایجاد میکنیم که بر اساس ورودی کاربر، نمونهای از یکی از این کلاسها را برمیگرداند. زیبایی این طرح در این است که فکتوری همواره شیئی را برمیگرداند که مطمئناً متدهای `process_payment` و `refund` را دارد. این امر وابستگی کد کلاینت به پیادهسازی خاص را از بین میبرد و آن را تنها به رابط تعریف شده توسط ABC وابسته میکند که یک اصل مهم در طراحی نرمافزار است.
استفاده از کلاسهای پایه انتزاعی یک گام فراتر از پیادهسازی ساده الگوی فکتوری است و آن را به سطحی حرفهای ارتقا میدهد. این تکنیک زمانی بسیار مناسب است که شما خانوادهای از کلاسهای مرتبط دارید که باید از یک رابط کاملاً مشخص و الزامآور پیروی کنند. اگر نیاز به تضمین اجرای صحیح قراردادها توسط تمام کلاسها دارید و میخواهید از خطاهای ناشی از فراموشی پیادهسازی متدها جلوگیری کنید، ABCها راهحل ایدهآلی هستند. در پروژههای بزرگ و پیچیده، این روش باعث افزایش قابلیت اطمینان، تسهیل تستنویسی و بهبود قابلیت نگهداری کد میشود. با این حال، برای پروژههای کوچک یا سناریوهای سادهای که تنها یک یا دو کلاس دارند، ممکن است این سطح از انتزاع لازم نباشد و پیادهسازی سادهتر فکتوری کفایت کند.
یکی از کاربردیترین نمونههای الگوی Factory در پایتون، مدیریت اتصالات مختلف به پایگاهداده است. فرض کنید برنامهای دارید که باید با چندین نوع دیتابیس مانند PostgreSQL، MySQL و SQLite کار کند. هرکدام از این دیتابیسها پارامترهای اتصال و منطق متفاوتی دارند. بدون استفاده از الگوی Factory، ممکن است مجبور شوید در سراسر کدتان شرطهای متعددی برای ایجاد اتصال مناسب بنویسید که این امر منجر به کد تکراری و پیچیده میشود.
با استفاده از الگوی Factory، میتوانید یک واسط یکپارچه برای ایجاد اتصال به انواع دیتابیسها ایجاد کنید. یک کلاس Factory تعریف میکنید که بر اساس پارامترهای ورودی (مانند نوع دیتابیس)، شیء اتصال مناسب را میسازد. این کلاس میتواند دارای متدهایی مانند create_connection برای دریافت پارامترها به صورت مستقیم و create_from_config برای خواندن تنظیمات از یک فایل پیکربندی باشد. این مرکزبندی، مدیریت و تغییر نوع دیتابیس را بدون نیاز به تغییر در کد اصلی برنامه، بسیار ساده میکند.
این الگو به ویژه هنگام توسعه برنامههای enterprise که نیاز به پشتیبانی از چندین پایگاهداده یا تغییر محیطهای توسعه، تست و تولید دارند، بسیار ارزشمند است. اگر تنظیمات دیتابیس در محیط تست شما SQLite و در محیط تولید PostgreSQL باشد، تنها با تغییر یک مقدار در فایل config و بدون هیچ تغییری در منطق برنامه، اتصال صحیح ایجاد میشود. این امر، وابستگی کد شما را به یک دیتابیس خاص کم میکند و قابلیت نگهداری و تستپذیری را به شدت افزایش میدهد.
الگوی Factory یک ابزار قدرتمند برای مدیریت فرآیند ایجاد اشیا در پایتون است. همانطور که در مثال اتصال به دیتابیس دیدیم، این الگو با جداسازی منطق ساختن اشیا از منطق کسبوکار، کدی تمیز، منعطف و قابل نگهداری ایجاد میکند. توصیه میشود هنگام مواجهه با سناریوهایی که شامل کلاسهای متعدد، تصمیمگیری در زمان اجرا یا منطق پیچیدهٔ ایجاد شیء هستند، از این الگو استفاده کنید. پیادهسازی سادهای با دیکشنری اغلب برای نیازهای معمول کافی است. با شروع از مثالهای پایه، به تدریج پیچیدگی را تنها در صورت لزوم اضافه کنید.