توضیح دوات: برای راهنمایی و اطلاعات بیشتر در مورد نمودار کلاس به این مقاله مراجعه کنید.
الگوی طراحی Abstract Factory یا کارخانه انتزاعی یک الگوی سازنده است که به ما این امکان را میدهد که خانوادهای از اشیاء مرتبط را بدون مشخص کردن کلاسهای خاص آنها تولید کنیم.
الگوی طراحی کارخانه انتزاعی الگویی است که برای فراهم کردن یک رابط برای ایجاد یک گروه از کارخانهها با یک مورد استفاده مشترک استفاده میشود، بدون آنکه کلاسهای خاص (Concrete classes) آنها مشخص شوند. هر پیادهسازی خاص از کارخانه انتزاعی، اشیاء خاصی تولید میکند که بخشی از همان خانواده محصولات هستند.
هنگامی که از این الگو استفاده میکنیم، کارخانههایی ایجاد میکنیم که انواع مختلف اشیاء مرتبط را برمیگردانند. این الگو امکان معماریهای بزرگتر مانند تزریق وابستگی (Dependency Injection) را فراهم میکند.
شما میتوانید کد مثال این پست را در گیتهاب پیدا کنید.
درک مطلب
تصور کنید که ما در حال ایجاد یک برنامه شبیهسازی برای یک فروشگاه مبلمان هستیم. کد ما شامل کلاسهایی است که یا یک خانواده از محصولات را نشان میدهند، مانند Armchairs
صندلیهای راحتی، Sofas
مبلها و Cupboards
کمدها، یا انواع مختلف آن خانوادهها، مانند Rustic
سبک روستیک، Urban Collective
جمعوجور شهری و Scandinavian Contemporary
اسکاندیناوی معاصر.
ما به روشی نیاز داریم تا اشیاء مبلمان فردی را ایجاد کنیم که با سایر اشیاء از همان خانواده مطابقت داشته باشند. در نهایت، مشتریان ممکن است اگر مبلمان غیرهمخوان دریافت کنند، خیلی ناراحت شوند. علاوه بر این، نمیخواهیم وقتی محصولات یا خانوادههای جدیدی را به برنامه اضافه میکنیم، کد موجود را تغییر دهیم.
اولین چیزی که الگوی کارخانه انتزاعی پیشنهاد میکند، این است که بهطور واضح رابطهایی برای هر محصول متمایز از خانواده محصول اعلام کنیم. سپس میتوانیم تمام انواع محصولات را به پیروی از همان رابطها وادار کنیم. بهعنوان مثال، تمام انواع صندلی راحتی میتوانند رابط Armchair
(صندلی راحتی) را پیادهسازی کنند؛ تمام انواع مبل میتوانند رابط Sofa
(مبل) را پیادهسازی کنند و به همین ترتیب.
مورد بعدی که باید انجام دهیم این است که کارخانه انتزاعی را اعلام کنیم، یک رابط با مجموعهای از روشهای ایجاد برای تمام محصولاتی که بخشی از خانواده محصول هستند. این روشها باید انواع محصول انتزاعی را برگردانند که توسط رابطهایی که قبلاً استخراج کردهایم، نمایش داده شدهاند: Armchair
(صندلی راحتی)، Sofa
(مبل)، Cupboard
(کمد) و غیره.
حالا به سراغ انواع محصولات میرویم. برای هر نوع از یک خانواده محصول، یک کلاس کارخانه جداگانه بر اساس رابط IFurnitureFactory
ایجاد میکنیم. یک کارخانه کلاسی است که محصولات یک نوع خاص را برمیگرداند. بهعنوان مثال، کارخانه RusticFurnitureFactory
فقط میتواند اشیاء RusticArmchair
(صندلی راحتی روستیک)، RusticSofa
(مبل روستیک) و RusticCupboard
(کمد روستیک) ایجاد کند.
کد مشتری باید با هر دو کارخانه و محصولات از طریق رابطهای انتزاعی مربوط به آنها کار کند. این اجازه میدهد که نوع کارخانهای که به کد مشتری میدهیم و همچنین نوع محصولی که کد مشتری دریافت میکند، بدون شکستن کد واقعی مشتری تغییر کند.
فرض کنید مشتری میخواهد کارخانهای برای تولید یک صندلی داشته باشد. مشتری نیازی به آگاهی از کلاس کارخانه ندارد و همچنین مهم نیست که چه نوع صندلی دریافت میکند. چه صندلی راحتی اسکاندیناوی معاصر باشد و چه صندلی راحتی جمعوجور شهری، مشتری باید همه صندلیها را به یک شیوه مشابه در نظر بگیرد و از رابط انتزاعی IArmchair
(صندلی راحتی) استفاده کند. با این رویکرد، مشتری تنها میداند که محصول بهنوعی از روش SitOn استفاده میکند. همچنین، هر نوع صندلی که برگشت داده شود، همیشه با نوع مبل یا کمدی که توسط همان شیء کارخانه تولید میشود، مطابقت خواهد داشت.
یک نکته آخر برای روشن کردن: چه کسی اشیاء کارخانه واقعی را ایجاد میکند؟ معمولاً، برنامه در مرحله راهاندازی یک شیء کارخانه خاص ایجاد میکند. درست قبل از آن، برنامه باید نوع کارخانه را بسته به پیکربندی یا تنظیمات محیط انتخاب کند.
ساختار الگوی طراحی Abstract factory
در پیادهسازی پایهای، الگوی کارخانه انتزاعی پنج شرکتکننده دارد:
- محصول انتزاعی: محصول انتزاعی رابطهایی برای مجموعهای از محصولات متمایز ولی مرتبط که یک خانواده محصول را تشکیل میدهند، اعلام میکند.
- محصول خاص: محصولات خاص پیادهسازیهای مختلفی از محصولات انتزاعی هستند که بر اساس انواع گروهبندی میشوند. هر محصول انتزاعی (صندلی راحتی/مبل) باید در تمام انواع داده شده (روستیک/اسکاندیناوی معاصر) پیادهسازی شود.
- کارخانه انتزاعی: رابط کارخانه انتزاعی مجموعهای از روشها برای ایجاد هر یک از محصولات انتزاعی را اعلام میکند.
- کارخانه خاص: کارخانههای خاص روشهای ایجاد کارخانه انتزاعی را پیادهسازی میکنند. هر کارخانه خاص مربوط به یک نوع خاص از محصولات است و فقط آن نوع محصولات را ایجاد میکند.
- مشتری: مشتری میتواند با هر کارخانه خاص/نوع محصولی کار کند، به شرطی که با اشیاء آنها از طریق رابطهای انتزاعی ارتباط برقرار کند. هرچند که کارخانههای خاص محصولات خاص را نمونهسازی میکنند، امضاهای روشهای ایجاد آنها باید محصولات انتزاعی مربوطه را بازگردانند. به این ترتیب کد مشتری که از یک کارخانه استفاده میکند، به نوع خاص محصولی که از کارخانه دریافت میکند، وابسته نخواهد شد.
برای نشان دادن نحوه کارکرد الگوی کارخانه انتزاعی، ما قصد داریم یک برنامه کامپوننت GUI (رابط گرافیکی کاربر) کراسپلتفرم ایجاد کنیم.
برای شروع، ابتدا سلسلهمراتب محصول اول خود را ایجاد خواهیم کرد، یعنی دکمه. ما یک رابط IButton
(دکمه) ایجاد خواهیم کرد که شرکتکننده محصول انتزاعی است و سه دکمه LinuxButton
(دکمه لینوکس)، MacOSButton
(دکمه مکاواس) و WindowsButton
(دکمه ویندوز)، که محصولات خاص هستند، ایجاد خواهیم کرد.
namespace AbstractFactory.Buttons
{
///
/// The Abstract Factory pattern assumes that we have several families
/// of products, structured into separate class hierarchies (Button/Checkbox/Panel).
/// All products of the same family have a common interface.
///
/// This is the common interface for buttons family.
///
public interface IButton
{
public void Paint();
}
}
namespace AbstractFactory.Buttons
{
///
/// All product families have the same varieties (Linux/MacOS/Windows).
///
/// This is a Linux variant of a button.
///
public class LinuxButton : IButton
{
public void Paint()
{
Console.WriteLine("Successfully created a LinuxButton");
}
}
}
namespace AbstractFactory.Buttons
{
///
/// All product families have the same varieties (Linux/MacOS/Windows).
///
/// This is a MacOS variant of a button.
///
public class MacOSButton : IButton
{
public void Paint()
{
Console.WriteLine("Successfully created a MacOSButton");
}
}
}
namespace AbstractFactory.Buttons
{
///
/// All product families have the same varieties (Linux/MacOS/Windows).
///
/// This is a Windows variant of a button.
///
public class WindowsButton : IButton
{
public void Paint()
{
Console.WriteLine("Successfully created a WindowsButton");
}
}
}
مجموعه دیگری از محصولات انتزاعی و پیادهسازی آنها ایجاد خواهیم کرد. این مجموعه چکباکسها هستند.
namespace AbstractFactory.Checkboxes
{
///
/// Checkboxes are the second product family.
/// It has the same variants as buttons.
///
public interface ICheckbox
{
public void Paint();
}
}
namespace AbstractFactory.Checkboxes
{
///
/// All product families have the same varieties (Linux/MacOS/Windows)
///
/// This is a variant of a checkbox.
///
public class LinuxCheckbox : ICheckbox
{
public void Paint()
{
Console.WriteLine("Successfully created a Linux checkbox");
}
}
}
namespace AbstractFactory.Checkboxes
{
///
/// All product families have the same varieties (Linux/MacOS/Windows)
///
/// This is a variant of a checkbox.
///
public class MacOSCheckbox : ICheckbox
{
public void Paint()
{
Console.WriteLine("Successfully created a MacOS checkbox");
}
}
}
namespace AbstractFactory.Checkboxes
{
///
/// All product families have the same varieties (Linux/MacOS/Windows)
///
/// This is a variant of a checkbox.
///
public class WindowsCheckbox : ICheckbox
{
public void Paint()
{
Console.WriteLine("Successfully created a WindowsCheckbox");
}
}
}
و آخرین مجموعه محصولات که پنلها باشند:
namespace AbstractFactory.Panels
{
///
/// Panels is the third product family. It has the same variants as buttons and checkboxes.
///
public interface IPanel
{
public void Paint();
}
}
namespace AbstractFactory.Panels
{
///
/// All product families have the same varieties (Linux/MacOS/Windows)
///
/// This is a variant of a panel.
///
public class LinuxPanel : IPanel
{
public void Paint()
{
Console.WriteLine("Successfully created a LinuxPanel");
}
}
}
namespace AbstractFactory.Panels
{
///
/// All product families have the same varieties (Linux/MacOS/Windows)
///
/// This is a variant of a panel.
///
public class MacOSPanel : IPanel
{
public void Paint()
{
Console.WriteLine("Successfully created a MacOSPanel");
}
}
}
namespace AbstractFactory.Panels
{
///
/// All product families have the same varieties (Linux/MacOS/Windows)
///
/// This is a variant of a panel.
///
public class WindowsPanel : IPanel
{
public void Paint()
{
Console.WriteLine("Successfully created a WindowsPanel");
}
}
}
قدم بعدی، ایجاد شرکتکنندههای کارخانه انتزاعی است. رابطی برای ایجاد چکباکس (ICheckbox
)، دکمه (IButton
) و پنل (IPanel
) ایجاد خواهیم کرد.
using AbstractFactory.Buttons;
using AbstractFactory.Checkboxes;
using AbstractFactory.Panels;
namespace AbstractFactory.Factories
{
///
/// Abstract factory knows about all (abstract) product types
///
public interface IGUIFactory
{
public IButton CreateButton();
public ICheckbox CreateCheckbox();
public IPanel CreatePanel();
}
}
حالا شروع به پیادهسازی شرکتکنندگان کارخانه خاص میکنیم.
using AbstractFactory.Buttons;
using AbstractFactory.Checkboxes;
using AbstractFactory.Panels;
namespace AbstractFactory.Factories
{
///
/// Each concrete factory extends basic factory and responsible for creating
/// products of a single variety
///
public class LinuxFactory : IGUIFactory
{
public IButton CreateButton()
{
return new LinuxButton();
}
public ICheckbox CreateCheckbox()
{
return new LinuxCheckbox();
}
public IPanel CreatePanel()
{
return new LinuxPanel();
}
}
}
using AbstractFactory.Buttons;
using AbstractFactory.Checkboxes;
using AbstractFactory.Panels;
namespace AbstractFactory.Factories
{
///
/// Each concrete factory extends basic factory and responsible for creating
/// products of a single variety.
///
public class MacOSFactory : IGUIFactory
{
public IButton CreateButton()
{
return new MacOSButton();
}
public ICheckbox CreateCheckbox()
{
return new MacOSCheckbox();
}
public IPanel CreatePanel()
{
return new MacOSPanel();
}
}
}
using AbstractFactory.Buttons;
using AbstractFactory.Checkboxes;
using AbstractFactory.Panels;
namespace AbstractFactory.Factories
{
///
/// Each concrete factory extends basic factory and responsible for creating
/// products of a single variety
///
public class WindowsFactory : IGUIFactory
{
public IButton CreateButton()
{
return new WindowsButton();
}
public ICheckbox CreateCheckbox()
{
return new WindowsCheckbox();
}
public IPanel CreatePanel()
{
return new WindowsPanel();
}
}
}
آخرین مرحله این است که به برنامه مشتری این امکان را بدهیم که محصولات مناسب را دریافت کند. برنامه اهمیتی نمیدهد که از کدام کارخانه خاص استفاده خواهد کرد. تنها چیزی که نیاز دارد یک رابط برای دسترسی به محصولات است:
using AbstractFactory.Buttons;
using AbstractFactory.Checkboxes;
using AbstractFactory.Factories;
using AbstractFactory.Panels;
namespace AbstractFactory.App
{
///
/// Factory users don't care which concrete factory they use since they work with
/// factories and products through abstract interfaces.
///
public class Application
{
private IButton button;
private ICheckbox checkbox;
private IPanel panel;
public Application(IGUIFactory factory)
{
button = factory.CreateButton();
checkbox = factory.CreateCheckbox();
panel = factory.CreatePanel();
}
public void Paint()
{
button.Paint();
checkbox.Paint();
panel.Paint();
}
}
}
در نهایت متد Main()
را پیادهسازی میکنیم.
using AbstractFactory.App;
using AbstractFactory.Factories;
using System.Runtime.InteropServices;
public class Program
{
public static void Main(string[] args)
{
Application app = ConfigureApplication();
app.Paint();
}
private static Application ConfigureApplication()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return new Application(new LinuxFactory());
if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return new Application(new MacOSFactory());
return new Application(new WindowsFactory());
}
}
مزایا و معایب الگوی طراحی کارخانه انتزاعی
مزایا
- میتوانیم اطمینان داشته باشیم که محصولاتی که از یک کارخانه دریافت میکنیم، با یکدیگر سازگارند.
- از اتصال محکم بین محصولات خاص و کد مشتری جلوگیری میکنیم.
- میتوانیم کد ایجاد محصول را در یک مکان متمرکز کنیم، که باعث میشود کد قابل پشتیبانیتر شود و این امر با احترام به اصل مسئولیت واحد انجام میشود.
- میتوانیم انواع جدید محصولات را بدون شکستن کد موجود مشتری معرفی کنیم، که این امر اصل باز/بسته را رعایت میکند.
معایب
- کد ممکن است پیچیدهتر از حد لازم شود، چرا که با این الگو، مجموعهای از رابطها و کلاسهای جدید معرفی میشود.
رابطه با الگوهای دیگر
- بسیاری از طراحیها با استفاده از متد کارخانه (Factory method) شروع میکنند و به سمت کارخانه انتزاعی، پروتوتایپ (Prototype) یا سازنده (Builder) توسعه مییابند.
- کلاسهای کارخانه انتزاعی اغلب بر اساس مجموعهای از متدهای کارخانه (Factory method) ساخته میشوند.
- سازنده بر ساخت شیء پیچیده به صورت گام به گام تمرکز دارد. کارخانه انتزاعی متخصص در ایجاد خانوادهای از اشیاء مرتبط است. کارخانه انتزاعی بلافاصله محصول را باز میگرداند، در حالی که سازنده (Builder) اجازه میدهد که گامهای اضافی ساخت انجام شود قبل از دریافت محصول.
- کارخانه انتزاعی میتواند به عنوان جایگزینی برای نما (Facade) عمل کند زمانی که تنها بخواهیم نحوه ایجاد اشیاء زیرسیستم را از کد مشتری مخفی کنیم.
- میتوانیم از کارخانه انتزاعی همراه با الگوی پل (Bridge) استفاده کنیم. این ترکیب زمانی مفید است که برخی از انتزاعها که توسط پل تعریف شدهاند، فقط با پیادهسازیهای خاصی کار کنند. در این حالت، کارخانه انتزاعی میتواند این روابط را محصور کند و پیچیدگی را از کد مشتری پنهان کند.
نتیجهگیری
در این مقاله، ما به بررسی الگوی کارخانه انتزاعی پرداختیم، زمان مناسب برای استفاده از آن و مزایا و معایب استفاده از این الگوی طراحی را مورد بحث قرار دادیم. همچنین روابط بین کارخانه انتزاعی و سایر الگوها را بررسی کردیم.
کارخانه انتزاعی یک الگوی طراحی بسیار مفید است که هنگام کار با خانوادهای از اشیاء که توسط یک کارخانه ایجاد میشوند، قابل پیادهسازی است.
اگر نیاز به مخفی کردن جزئیات ایجاد اشیاء دارید یا اگر بخواهید نوع اشیاء ایجاد شده را در زمان اجرا تغییر دهید، این الگو گزینه مناسبی است. با اندکی تفکر و برنامهریزی، الگوی طراحی کارخانه انتزاعی میتواند افزودهای ارزشمند به جعبه ابزار شما باشد.
شایان ذکر است که الگوی کارخانه انتزاعی، همراه با سایر الگوهای طراحی ارائه شده توسط Gang of Four، یک درمان قطعی یا راهحل نهایی در طراحی یک برنامه نیست. بار دیگر، این بر عهده مهندسان است که تصمیم بگیرند کجا باید از یک الگوی خاص استفاده کنند. در نهایت، این الگوها زمانی مفید هستند که به عنوان ابزاری دقیق استفاده شوند، نه چکش بزرگ!