زمان تخمینی مطالعه: 13 دقیقه
الگوی Abstract Factory در مهندسی نرمافزار یک Design Pattern است که راهی برای ایجاد خانوادههای اشیاء مرتبط بدون تحمیل کلاسهای Concrete آنها، با کپسولهسازی گروهی از کارخانههای(Factories) منفرد که دارای یک موضوع مشترک هستند، بدون مشخص کردن کلاسهای Concrete خود، فراهم میکند. بر اساس این الگو که از دسته Creational است، یک جزء نرم افزار مشتری، یک پیاده سازی ملموس از Abstract Factory ایجاد میکند و سپس از اینترفیس عمومی کارخانه برای ایجاد اشیاء Concrete که بخشی از خانواده هستند استفاده میکند.
در حالت کلی مشتری نمیداند که از هر یک از این کارخانههای داخلی کدام اشیاء concrete را دریافت میکند، زیرا فقط از اینترفیسهای عمومی محصولات خود استفاده میکند. این الگو جزئیات اجرای مجموعهای از اشیاء را از کاربرد عمومی آنها جدا میکند و بر ترکیب شیء تکیه میکند، زیرا ایجاد شی در روشهایی که در اینترفیس کارخانه اجرا میشوند، انجام میشود. استفاده از این الگو پیادهسازی concrete قابل تعویض را بدون تغییر کدی که از آنها استفاده میکند، حتی در زمان اجرا، امکان پذیر میکند. با این حال، استفاده از این الگو، مانند الگوهای طراحی مشابه، ممکن است منجر به پیچیدگی غیر ضروری و کار اضافی در نوشتن اولیه کد شود. علاوه بر این، سطوح بالاتر جداسازی و انتزاع میتواند منجر به سیستمهایی شود که اشکالزدایی و نگهداری آنها دشوارتر است.
بیان مسئله: تصور کنید که در حال ایجاد یک شبیه ساز مغازه مبلمان فروشی هستید. کد نوشته شده توسط شما شامل کلاسهایی است که نشان دهنده:
- یک خانواده از محصولات مرتبط شامل Chair + Sofa+ CoffeeTable است.
- چندین گونه یا تنوع از این خانواده وجود دارد. به عنوان مثال، محصولات Chair + Sofa + CoffeeTable در این ساختار در انواع و دستههای متنوعی موجود هستند که شامل: Modern، Victorian، ArtDeco هستند.
شما برای مقابله با این مسئله به راهی برای ایجاد اشیاء مبلمان فردی نیاز دارید تا با سایر اشیاء هم خانواده مطابقت داشته باشد. در نظر داشته باشید که مشتریان با دریافت مبلمان نامتناسب و اشتباه بسیار عصبانی میشوند.
همچنین، هنگام افزودن محصولات جدید یا خانواده محصولات به برنامه، نمیخواهید کد موجود نوشته شده را تغییر دهید. به عنوان مثال فروشندگان مبلمان اغلب کاتالوگهای خود را به روز میکنند و شما نیز نمیخواهید هر بار که به روزرسانی اتفاق میافتد کد اصلی را تغییر دهید.
اولین چیزی که الگوی Abstract Factory پیشنهاد میکند این است که به صراحت اینترفیسها را برای هر محصول متمایز از خانواده محصول (مانند صندلی، مبل یا میز قهوه) اعلان(Declare) کنید. سپس میتوانید کاری کنید که همه انواع محصولات از آن اینترفیسها پیروی کنند. به عنوان مثال، همه انواع صندلی میتوانند اینترفیس Chair را پیاده سازی کنند و یا همه انواع میز قهوه میتوانند رابط CoffeeTable و غیره را پیادهسازی کنند.
گام بعدی اعلان Abstract Factory است که یک اینترفیس با لیستی از متدهای creation برای همه محصولاتی است که بخشی از خانواده محصول هستند (به عنوان مثال، createChair، createSofa و createCoffeeTable). این متدها باید انواع محصول Abstract را بازگردانند که با اینترفیسهایی که قبلا استخراج کردهایم نشان داده میشوند: مانند Chair، Sofa، CoffeeTable و غیره.
حال، انواع محصولات را چگونه مشخص کنیم؟ برای هر گونه از یک خانواده محصول، ما یک کلاس کارخانه(factory) جداگانه بر اساس اینترفیس AbstractFactory ایجاد میکنیم. کارخانه کلاسی است که محصولاتی از نوع خاصی را بر میگرداند. به عنوان مثال، ModernFurnitureFactory فقط میتواند اشیاء ModernChair، ModernSofa و ModernCoffeeTable ایجاد کند.
کد مشتری باید با کارخانهها و محصولات از طریق اینترفیسهای انتزاعی مربوطه کار کند. این قابلیت به شما امکان میدهد نوع کارخانهای را که به کد مشتری ارسال میکنید، و همچنین نوع محصولی که کد مشتری دریافت میکند، بدون شکستن کد مشتری واقعی، تغییر دهید.
به عنوان مثال بگویید “مشتری یک کارخانه برای تولید صندلی میخواهد”. در این حالت مشتری لازم نیست از کلاس کارخانه آگاه باشد و همچنین مهم نیست که چه نوع صندلی میگیرد. چه یک مدل مدرن باشد یا یک صندلی به سبک ویکتوریایی، مشتری باید با استفاده از اینترفیس انتزاعی Chair با همه صندلیها به یک روش رفتار کند. با این رویکرد، تنها چیزی که مشتری در مورد صندلی میداند این است که متد sitOn را به نوعی پیادهسازی میکند. همچنین، هر نوع صندلی که برگردانده شود، همیشه با نوع مبل یا میز قهوهخوری تولید شده توسط همان شی کارخانه مطابقت دارد.
یک موضوع دیگر باقی مانده است تا همه چیز به وضوح مشخص شود و آن این است که اگر مشتری فقط در معرض اینترفیسهای انتزاعی باشد، چه مکانیزمی اشیاء کارخانه واقعی را ایجاد میکند؟ معمولاً برنامه در مرحله شروع و مقدار دهی اولیه یک شی کارخانه concrete ایجاد میکند. درست قبل از آن، برنامه باید بسته به ترجیحات یا تنظیمات محیط، نوع کارخانه(Factory) را انتخاب کند.
برای تشریح بیشتر و روشن شدن موضوع بر اساس اصول مهندسی نرم افزار ساختار این الگوی طراحی را به صورت UML در تصویر زیر نشان دادهایم همچنین توضیحات هر گام به طور مشخص در زیر این تصویر نوشته شده است.
- گام 1: محصولات Abstract اینترفیس را برای مجموعهای از محصولات متمایز اما مرتبط که یک خانواده محصول را تشکیل میدهند، اعلام(Declare) میکنند.
- گام 2: محصولات Concrete پیادهسازیهای مختلفی از محصولات انتزاعی(Abstract) هستند که بر اساس انواع مختلف گروهبندی میشوند. هر محصول انتزاعی (صندلی/مبل) باید در تمام انواع دستههای داده شده (ویکتوریایی/مدرن) پیادهسازی شود.
- گام 3: اینترفیس الگوی Abstract Factory مجموعهای از متدها را برای ایجاد هر یک از محصولات انتزاعی اعلام میکند.
- گام 4: کارخانههای concrete متدهای ایجاد Abstract Factory را اجرا میکنند. هر کارخانه concrete مربوط به یک نوع خاص از محصولات است و تنها آن گونههای محصول را ایجاد میکند.
- گام 5: اگرچه کارخانههای concrete محصولات concrete را نمونهسازی میکنند، اما امضای روشهای ایجاد آنها باید محصولات انتزاعی مربوطه را بازگرداند. به این ترتیب کد مشتری که از یک کارخانه استفاده میکند قابلیت جفت شدن با نوع خاصی از محصولی که از یک کارخانه دریافت میکند را ندارد. مشتری تا زمانی که با اشیاء آنها از طریق اینترفیسهای انتزاعی ارتباط برقرار کند، میتواند با هر نوع کارخانه/محصول concrete کار کند.
شبه کد(Pseudocode)
در این مثال نشان خواهیم داد که چگونه میتوان از الگوی Abstract Factory برای ایجاد عناصر رابط کاربری(UI) مستقل از پلتفرم بدون جفت کردن(coupling) کد مشتری با کلاسهای رابط کاربری مشخص استفاده کرد، در حالی که همه عناصر ایجاد شده را با یک سیستم عامل انتخابی سازگار نگه میدارد.
در حالت کلی انتظار میرود که همان عناصر رابط کاربری در یک برنامه مستقل از پلتفرم به طور مشابه رفتار کنند، اما در سیستمعاملهای مختلف کمی متفاوت به نظر میرسند. علاوه بر این، این وظیفه برنامه نویس است که مطمئن شود عناصر رابط کاربری با استایل سیستم عامل فعلی مطابقت دارند. به عبارت دیگر وقتی برنامه در ویندوز اجرا میشود، اجرای کنترلهای macOS به هیچ عنوان مطلوب نیست!!!
اینترفیس Abstract Factory مجموعهای از متدهای ایجاد را اعلام میکند که کد مشتری(Client Code) میتواند برای تولید انواع مختلف عناصر UI از آنها استفاده کند. کارخانههای concrete با سیستم عاملهای خاصی مطابقت دارند و عناصر UI را ایجاد میکنند که با آن سیستم عامل خاص مطابقت دارد. در واقع نحوه انجام این کار به این صورت است که هنگامی راه اندازی یک برنامه، نوع سیستم عامل فعلی را بررسی میکند. سپس برنامه از این اطلاعات برای ایجاد یک شی کارخانه از کلاسی استفاده میکند که با سیستم عامل مطابقت دارد. بقیه کدها از این کارخانه(factory) برای ایجاد عناصر UI استفاده میکنند. این مکانیزم از ایجاد عناصر اشتباه در طی فرآیند اجرای برنامه جلوگیری میکند.
// The abstract factory interface declares a set of methods that
// return different abstract products. These products are called
// a family and are related by a high-level theme or concept.
// Products of one family are usually able to collaborate among
// themselves. A family of products may have several variants,
// but the products of one variant are incompatible with the
// products of another variant.
interface GUIFactory is
method createButton():Button
method createCheckbox():Checkbox
// Concrete factories produce a family of products that belong
// to a single variant. The factory guarantees that the
// resulting products are compatible. Signatures of the concrete
// factory's methods return an abstract product, while inside
// the method a concrete product is instantiated.
class WinFactory implements GUIFactory is
method createButton():Button is
return new WinButton()
method createCheckbox():Checkbox is
return new WinCheckbox()
// Each concrete factory has a corresponding product variant.
class MacFactory implements GUIFactory is
method createButton():Button is
return new MacButton()
method createCheckbox():Checkbox is
return new MacCheckbox()
// Each distinct product of a product family should have a base
// interface. All variants of the product must implement this
// interface.
interface Button is
method paint()
// Concrete products are created by corresponding concrete
// factories.
class WinButton implements Button is
method paint() is
// Render a button in Windows style.
class MacButton implements Button is
method paint() is
// Render a button in macOS style.
// Here's the base interface of another product. All products
// can interact with each other, but proper interaction is
// possible only between products of the same concrete variant.
interface Checkbox is
method paint()
class WinCheckbox implements Checkbox is
method paint() is
// Render a checkbox in Windows style.
class MacCheckbox implements Checkbox is
method paint() is
// Render a checkbox in macOS style.
// The client code works with factories and products only
// through abstract types: GUIFactory, Button and Checkbox. This
// lets you pass any factory or product subclass to the client
// code without breaking it.
class Application is
private field factory: GUIFactory
private field button: Button
constructor Application(factory: GUIFactory) is
this.factory = factory
method createUI() is
this.button = factory.createButton()
method paint() is
button.paint()
// The application picks the factory type depending on the
// current configuration or environment settings and creates it
// at runtime (usually at the initialization stage).
class ApplicationConfigurator is
method main() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
factory = new WinFactory()
else if (config.OS == "Mac") then
factory = new MacFactory()
else
throw new Exception("Error! Unknown operating system.")
Application app = new Application(factory)
با این رویکرد، کد کلاینت تا زمانی که با این اشیا از طریق اینترفیسهای انتزاعی آنها کار میکند، به کلاسهای مشخصی از کارخانهها و عناصر UI بستگی ندارد. این روش همچنین به کد مشتری اجازه میدهد از کارخانهها یا عناصر رابط کاربری دیگری که ممکن است در آینده به پروژه اضافه شود، پشتیبانی کند. در نتیجه، هر بار که تنوع جدیدی از عناصر رابط کاربری را به برنامه اضافه میشود، نیازی به تغییر کد مشتری وجود ندارد. فقط باید یک کلاس کارخانه جدید ایجاد ش که این عناصر را تولید کند و کمی کد اولیه برنامه را تغییر دهید تا در صورت لزوم آن کلاس را انتخاب کند.
قابلیتها و کاربردها
- از الگوی Abstract Factory زمانی استفاده میشود که کد موجود باید با خانوادههای مختلف محصولات مرتبط کار کند، اما نمیخواهیم به کلاسهای concrete آن محصولات بستگی داشته باشد. در واقع ممکن است از قبل ناشناخته باشند یا شما به سادگی میخواهید امکان توسعهپذیری در آینده را فراهم کنید. الگوی Abstract Factory یک اینترفیس برای ایجاد اشیاء از هر کلاس از خانواده محصول در اختیار شما قرار میدهد. تا زمانی که کد شما اشیاء را از طریق این اینترفیس ایجاد میکند، لازم نیست نگران ایجاد نوع اشتباه محصولی باشید که با محصولاتی که قبلاً توسط برنامه شما ایجاد شده مطابقت ندارد.
- اجرای الگوی Abstract Factory را زمانی در نظر بگیرید که کلاسی با مجموعهای از متدهای Factory دارید که باعث کم رنگ شدن مسئولیت اصلی آن شده است. معمولا در یک برنامه با طراحی بهینه، هر کلاس فقط مسئول یک کار است. هنگامی که یک کلاس با چندین نوع محصول سر و کار دارد، ممکن است ارزش استخراج متدهای کارخانهای آن و قرار دادن در یک کلاس کارخانه مستقل یا اجرای کامل Abstract Factory داشته باشد.
نحوه پیادهسازی
- ماتریسی از انواع محصولات متمایز در مقابل انواع این محصولات را نگاشت کنید.
- اینترفیسهای محصول انتزاعی را برای همه انواع محصول اعلام(declare) کنید. سپس تمام کلاسهای محصول concrete را وادار به پیادهسازی این اینترفیسها کنید.
- رابط abstract factory را با مجموعهای از متدهای ایجادی برای همه محصولات انتزاعی اعلام کنید.
- مجموعهای از کلاسهای concrete کارخانه، یکی برای هر نوع محصول را اجرا کنید.
- کد راه اندازی کارخانه را در جایی از برنامه ایجاد کنید. بسته به پیکربندی برنامه یا محیط فعلی، باید یکی از کلاسهای concrete کارخانه را نمونه سازی کند. این شی کارخانه را به تمام کلاسهایی که محصولات را می سازند منتقل کنید.
- کد را اسکن کنید و تمام فراخوانهای مستقیم با سازندههای محصول را بیابید. آنها را با فراخوانی به روش ایجاد مناسب در شی کارخانه جایگزین کنید.
مزایا و معایب الگوی Abstract Factory
این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:
– مزایا
- میتوانید مطمئن باشید که محصولاتی که از یک کارخانه دریافت میکنید با یکدیگر سازگار هستند.
- شما از اتصال محکم بین محصولات concrete و کد مشتری اجتناب میکنید.
- اصل مسئولیت واحد: میتوانید کد ایجاد محصول به یک مکان منتقل کنید و پشتیبانی از کد را آسانتر کنید.
- اصل باز/بسته: شما میتوانید انواع جدیدی از محصولات را بدون شکستن کد مشتری موجود معرفی کنید.
– معایب
- کد ممکن است پیچیدهتر از آن چیزی باشد که باید باشد، زیرا بسیاری از اینترفیسها و کلاسهای جدید همراه با الگو معرفی میشوند.
ارتباط با الگوهای دیگر
- بسیاری از الگوها با استفاده از متد Factory (کمتر پیچیدهتر و قابل تنظیمتر از طریق زیر کلاسها) شروع میشوند و به سمت Abstract Factory، Prototype یا Builder (انعطافپذیرتر، اما پیچیدهتر) تکامل مییابند.
- الگوی Builder بر ساختن اشیاء پیچیده گام به گام تمرکز میکند. Abstract Factory در ایجاد خانواده از اشیاء مرتبط تخصص دارد. Abstract Factory محصول را فوراً برمیگرداند، در حالی که Builder به شما امکان میدهد قبل از واکشی محصول، مراحل ساخت و ساز اضافی را اجرا کنید.
- کلاسهای الگوی Abstract Factory اغلب بر اساس مجموعهای از متدهای Factory هستند، اما شما همچنین میتوانید از الگوی Prototype برای ترکیب متدهای این کلاسها استفاده کنید.
- Abstract Factory میتواند به عنوان جایگزینی برای الگوی Facade عمل کند که در آن شما فقط میخواهید نحوه ایجاد اشیاء زیرسیستم را از کد مشتری پنهان کنید.
- میتوانید از الگوی Abstract Factory در کنار الگوی Bridge استفاده کنید. این جفت شدن زمانی مفید است که برخی از انتزاعات تعریف شده توسط Bridge فقط میتوانند با پیادهسازیهای خاص کار کنند. در این مورد، Abstract Factory میتواند این روابط را کپسوله کند و پیچیدگی را از کد کلاینت پنهان کند.
- الگوهایAbstract Factory، الگوی Builder و الگوی Prototype همگی میتوانند بهعنوان الگوهای Singleton پیادهسازی شوند.