توضیح دوات: برای راهنمایی و اطلاعات بیشتر در مورد نمودار کلاس به این مقاله مراجعه کنید.
الگوی طراحی Factory Method یک الگوی طراحی سازنده (Creational Design Pattern) است که امکان انتزاع در ایجاد اشیا را فراهم میکند. این روش، متدی برای ایجاد نمونهها بر اساس یک کلاس پایه تعریف میکند، اما به زیرکلاسها اجازه میدهد نوع اشیایی را که ایجاد میکنند تغییر دهند.
نمونه کد مربوط به این مطلب در GitHub موجود است.
درک مسئله
تصور کنید که در یک شرکت لجستیکی کار میکنیم که از ما خواسته است یک اپلیکیشن مدیریت لجستیک طراحی کنیم. این شرکت کوچک است و چند کامیون برای حملونقل دارد. بنابراین، اپلیکیشن ما فعلاً فقط میتواند کالاها را از طریق کامیون حمل کند و بخش عمدهای از کد ما در کلاس Truck
قرار دارد.
به لطف اپلیکیشن ما، شرکت پیشرفت میکند و بهصورت تصاعدی رشد میکند. در نهایت، شرکت یک کشتی باری خریداری میکند و اکنون میخواهد قابلیت لجستیک دریایی را نیز به اپلیکیشن اضافه کند.
این خبر خوبی است، اما وضعیت کد چگونه خواهد بود؟ در حال حاضر، بیشتر کد ما به کلاس Truck
وابسته است. اضافه کردن کلاس Ship
به اپلیکیشن نیازمند تغییرات معماری در تمام کد خواهد بود. علاوه بر این، اگر بعدها بخواهیم نوع دیگری از حملونقل را به اپلیکیشن اضافه کنیم، احتمالاً مجبور خواهیم شد دوباره همین تغییرات را اعمال کنیم.
در نتیجه، با یک کد پایه پیچیده و ناخوشایند روبرو خواهیم شد که پر از شرطهایی است که رفتار اپلیکیشن را بسته به نوع حملونقل تغییر میدهند.
راهحل الگوی طراحی Factory Method
الگوی Factory Method پیشنهاد میدهد که به جای ایجاد مستقیم اشیا (با استفاده از عملگر new
)، از یک متد کارخانهای استفاده کنیم. اشیایی که توسط متد کارخانه ایجاد میشوند اغلب بهعنوان “محصولات” (Products) شناخته میشوند.
در ابتدا، این تغییر چندان منطقی به نظر نمیرسد: ما صرفاً فراخوانی سازنده (Constructor) را از یک بخش کد به بخش دیگر منتقل کردهایم. اما اکنون میتوانیم متد کارخانه را در یک زیرکلاس بازنویسی کنیم و نوع محصولات ایجادشده توسط متد را تغییر دهیم.
البته این راهحل محدودیتهایی دارد: زیرکلاسها ممکن است بتوانند انواع مختلفی از محصولات را برگردانند، اما این محصولات باید یک کلاس پایه یا اینترفیس مشترک داشته باشند.
در اپلیکیشن لجستیک ما، هر دو کلاس Truck
و Ship
باید یک اینترفیس مشترک، بهعنوان مثال ITransport
، را پیادهسازی کنند. این اینترفیس میتواند شامل متدی به نام Deliver
باشد. کلاسها میتوانند این اینترفیس را بهصورت متفاوتی پیادهسازی کنند: کلاس Truck
با تحویل بار از طریق زمین سروکار دارد، در حالی که کلاس Ship
با تحویل بار از طریق دریا.
کاربر الگوی طراحی Factory Method تفاوتی بین محصولات واقعی بازگرداندهشده توسط هر زیرکلاس نمیبیند. کاربر همه محصولات را بهعنوان اشیای انتزاعی ITransport
در نظر میگیرد و میداند که همه اشیای ITransport
باید متدی به نام Deliver
را پیادهسازی کنند. اما نحوه پیادهسازی آن برای هر کدام مهم نیست.
ساختار الگوی طراحی Factory Method
در پیادهسازی پایه، الگوی Factory Method شامل چهار مؤلفه اصلی است:
محصول (Product): محصول، یک اینترفیس تعریف میکند که برای تمام اشیایی که میتوانند توسط خالق (Creator) و زیرکلاسهای آن تولید شوند مشترک است.
محصولات مشخص (Concrete Products): محصولات مشخص، پیادهسازیهای مختلف اینترفیس محصول هستند.
خالق (Creator): کلاس خالق، متد کارخانهای را تعریف میکند که اشیای جدید محصول را برمیگرداند. نوع بازگشتی این متد باید با اینترفیس محصول مطابقت داشته باشد. میتوان متد کارخانهای را بهصورت انتزاعی تعریف کرد تا تمام زیرکلاسها ملزم به پیادهسازی نسخههای خودشان شوند. بهعنوان جایگزین، متد کارخانه در کلاس پایه میتواند انواع پیشفرض محصولات را برگرداند. توجه کنید که برخلاف نامش، ایجاد محصول مسئولیت اصلی خالق نیست. معمولاً کلاس خالق دارای منطق اصلی مرتبط با محصولات است. متد کارخانه به جدا کردن این منطق از کلاسهای محصول مشخص کمک میکند. برای مثال: یک شرکت بزرگ توسعه نرمافزار ممکن است بخشی برای آموزش توسعهدهندگان داشته باشد. با این حال، وظیفه اصلی شرکت همچنان نوشتن کد است، نه تولید توسعهدهندگان.
خالق مشخص (Concrete Creators): خالقان مشخص، متد کارخانه پایه را بازنویسی میکنند تا نوع متفاوتی از محصول را بازگردانند.
برای نشان دادن نحوه عملکرد الگوی Factory Method، قصد داریم… یک ساندویچ بسازیم. طبق تعریف ویکیپدیا:
ساندویچ غذایی است که معمولاً شامل سبزیجات، پنیر برشخورده یا گوشت است که بین برشهای نان یا بهطور کلی در ظرفی از نان قرار میگیرد.
برای شروع، برخی از اجزای محصول (Product) که مواد تشکیلدهنده ساندویچ هستند را پیادهسازی میکنیم. یک کلاس انتزاعی به نام Ingredient
ایجاد خواهیم کرد تا آنها را نشان دهد.
namespace FactoryMethod.Ingredients
{
///
/// The Product Participant
///
public abstract class Ingredient
{
}
}
حالا بیایید چند کلاس را برای نمایش مواد اولیه در ساندویچها (که شرکتکنندگان ConcreteProduct ما هستند) ایجاد کنیم:
namespace FactoryMethod.Ingredients
{
///
/// Concrete Product
///
public class Bread : Ingredient
{
}
}
namespace FactoryMethod.Ingredients
{
///
/// Concrete Product
///
public class CornedBeef :Ingredient
{
}
}
namespace FactoryMethod.Ingredients
{
///
/// Concrete Product
///
public class Sauerkraut : Ingredient
{
}
}
namespace FactoryMethod.Ingredients
{
///
/// Concrete Product
///
public class SwissCheese : Ingredient
{
}
}
namespace FactoryMethod.Ingredients
{
///
/// Concrete Product
///
public class ThousandIslandsSauce:Ingredient
{
}
}
آنچه که اکنون میخواهیم انجام دهیم، ایجاد یک کارخانه است که به ما اجازه دهد با استفاده از همان مجموعه مواد اولیه، انواع مختلفی از ساندویچها را بسازیم. چیزی که بین انواع ساندویچها تفاوت خواهد داشت، مقدار و ترتیب این مواد اولیه است.
ابتدا، بیایید یک کلاس انتزاعی به نام Sandwich
بسازیم که نمایانگر تمام انواع ممکن از ساندویچها باشد. این کلاس همان شرکتکننده Creator است:
using FactoryMethod.Ingredients;
namespace FactoryMethod.Sandwiches
{
///
/// Creator
///
public abstract class Sandwich
{
private List ingredients = new List();
public Sandwich()
{
CreateIngredients();
}
public abstract void CreateIngredients();
public List Ingredients
{
get { return ingredients; }
}
public void DisplayIngredients()
{
foreach (var ingredient in ingredients)
Console.WriteLine(ingredient.GetType().Name);
}
}
}
به متد CreateIngredients()
توجه کنید. این متد همان متد کارخانهای (Factory Method) است که نام این الگو از آن گرفته شده است. این متد در اینجا پیادهسازی نشده است، زیرا پیادهسازی آن به کلاسهای ConcreteCreator واگذار شده که اکنون باید آنها را تعریف کنیم. بیایید با یک ساندویچ روبن (Reuben sandwich) شروع کنیم:
using FactoryMethod.Ingredients;
namespace FactoryMethod.Sandwiches
{
///
/// Concrete Creator
///
public class ReubenSandwich : Sandwich
{
public override void CreateIngredients()
{
Ingredients.Add(new Bread());
Ingredients.Add(new CornedBeef());
Ingredients.Add(new Sauerkraut());
Ingredients.Add(new SwissCheese());
Ingredients.Add(new ThousandIslandsSauce());
Ingredients.Add(new Bread());
}
}
}
هر زمان که یک شیء از کلاس ReubenSandwich
ایجاد کنیم، میتوانیم متد CreateIngredients()
را فراخوانی کنیم تا مقدار و ترتیب صحیح مواد اولیه برای این ساندویچ ایجاد شود.
حالا بیایید یک ساندویچ پنیر کبابی (GrilledCheeseSandwich
) بسازیم:
using FactoryMethod.Ingredients;
namespace FactoryMethod.Sandwiches
{
public class GrilledCheeseSandwich : Sandwich
{
public override void CreateIngredients()
{
Ingredients.Add(new Bread());
Ingredients.Add(new SwissCheese());
Ingredients.Add(new Bread());
}
}
}
حالا میتوانیم در متد Main
ساندویچمان را بسازیم.
using FactoryMethod.Sandwiches;
Console.WriteLine("Reuben Sandwich");
ReubenSandwich reubenSandwich = new ReubenSandwich();
reubenSandwich.DisplayIngredients();
Console.WriteLine("\n---------------------------------\n");
Console.WriteLine("Grilled Cheese Sandwich");
GrilledCheeseSandwich grilledCheese = new GrilledCheeseSandwich();
grilledCheese.DisplayIngredients();
مقایسه کارخانههای مختلف
در اینترنت ارجاعات متعددی به اصطلاح کارخانه (Factory) وجود دارد. با این که ممکن است همه آنها شبیه به هم باشند و به یک مفهوم اشاره کنند، اما هرکدام معانی متفاوتی دارند.
بیایید تفاوتهای آنها را بررسی کنیم.
کارخانه (Factory)
اصطلاح کارخانه یک مفهوم مبهم است که بیشترین سردرگمی را ایجاد میکند. این اصطلاح معمولاً به یک تابع، متد، یا کلاس اشاره دارد که قرار است چیزی تولید کند. در بیشتر موارد، کارخانهها اشیا تولید میکنند، اما میتوانند فایلها، رکوردهای دیتابیس، لاگها و موارد دیگر نیز تولید کنند.
هر یک از موارد زیر ممکن است بهعنوان یک کارخانه شناخته شوند:
- یک تابع یا متد که رابط کاربری (GUI) یک برنامه را تولید میکند (برای مثال، همین مورد).
- یک کلاسی که کاربران را ایجاد میکند.
- یک متد استاتیک که یک سازنده (constructor) را به روش خاصی فراخوانی میکند.
- یکی از الگوهای طراحی (design patterns) ایجادگر.
هنگامی که فردی از اصطلاح “کارخانه” استفاده میکند، باید معنی آن از زمینه مشخص باشد، اما این اصطلاح همچنان بهویژه در پایگاههای کد قدیمی، باعث سردرگمی و مشکلات میشود.
متد ایجادگر (Creation Method)
جاشوا کریوسکی، متد ایجادگر را بهعنوان یک متد که اشیا را ایجاد میکند، تعریف کرده است. این به این معناست که نتیجه هر الگوی طراحی متد کارخانهای (factory method pattern)، یک متد ایجادگر خواهد بود، اما عکس آن همیشه صادق نیست.
بهعلاوه، میتوانیم اصطلاح متد ایجادگر را بهجای متد کارخانهای در هر جایی که مارتین فاولر در کتاب Refactoring از آن استفاده کرده است، جایگزین کنیم.
در واقعیت، متد ایجادگر فقط یک پوشش (wrapper) برای فراخوانی سازنده (constructor) است. ممکن است این متد صرفاً نامی داشته باشد که هدف ما را بهتر بیان کند. از سوی دیگر، ممکن است کد را از تغییرات در سازنده ایزوله کند. حتی ممکن است شامل منطق خاصی باشد که بهجای ایجاد اشیای جدید، اشیای موجود را بازگرداند.
بسیاری از افراد این متد را به دلیل اینکه اشیا را ایجاد میکند، متد کارخانهای مینامند. منطق آن واضح است: متد، اشیا را ایجاد میکند و چون همه کارخانهها اشیا تولید میکنند، پس این متد یک متد کارخانهای است. به همین دلیل، سردرگمی زیادی میان متدهای ایجادگر و متدهای کارخانهای وجود دارد.
متد Next
زیر یک متد ایجادگر است:
public class Number
{
private int _value;
public Number(int theValue)
{
_value = theValue;
}
public Number Next()
{
return new Number(_value + 1);
}
}
متد ایجادگر ایستا (Static Creation Method)
متد ایجادگر ایستا یک متد ایجادگر است که بهصورت ایستا (static) تعریف شده است.
در حالی که بسیاری از افراد این متدها را متدهای کارخانهای ایستا مینامند، این نامگذاری اشتباه است. الگوی طراحی متد کارخانهای (Factory Method) یک الگوی طراحی مبتنی بر وراثت (inheritance) است. اگر آن را ایستا کنید، دیگر نمیتوانید از طریق زیرکلاسها (subclassing) آن را گسترش دهید، که این کار هدف اصلی این الگو را از بین میبرد.
زمانی که یک متد ایجادگر ایستا یک شیء جدید بازمیگرداند، به نوعی به سازنده جایگزین (Alternative Constructor) تبدیل میشود.
این رویکرد در موارد زیر مفید است:
زمانی که نیاز به چند سازنده با اهداف متفاوت داریم که امضاهای یکسانی دارند. برای مثال، در زبانهایی مانند Java، C++، و C# داشتن هر دو سازنده
Random(int max)
وRandom(int min)
ممکن نیست. معمولترین راهحل برای این محدودیت، تعریف چند متد ایستا است که سازنده پیشفرض را فراخوانی کرده و مقادیر مناسب را تنظیم میکنند.زمانی که میخواهیم به جای ایجاد نمونههای جدید، از اشیای موجود استفاده کنیم. (برای مثال، الگوی Singleton را ببینید.) در اکثر زبانهای برنامهنویسی، سازندهها باید حتماً نمونههای جدید از کلاس ایجاد کنند. متد ایجادگر ایستا بهعنوان یک راهحل، میتواند تصمیم بگیرد که آیا نمونه جدیدی با فراخوانی سازنده ایجاد کند یا یک شیء موجود را از کش (cache) بازگرداند.
در مثال زیر، متد Load
یک متد ایجادگر ایستا است. این متد یک روش راحت برای بازیابی کاربران از دیتابیس ارائه میدهد.
public class User {
private string id;
private string name;
private string email;
private long phoneNumber;
public User(string id,
string name,
string email,
long phoneNumber)
{
this.id = id;
this.name = name;
this.email = email;
this.phoneNumber = phoneNumber;
}
public static User Load(string id)
{
(string, string, string, long) data = Database.LoadData(id);
return new User(data.Item1, data.Item2, data.Item3, data.Item4);
}
}
الگوی Simple Factory (کارخانه ساده)
الگوی کارخانه ساده یک کلاس را توصیف میکند که دارای یک متد ایجاد (creation method) است. این متد شامل یک شرط بزرگ (conditional) میشود که بر اساس پارامترهای متد تصمیم میگیرد کدام کلاس محصول (product class) ایجاد شود.
کارخانههای ساده معمولاً با کارخانههای عمومی یا یکی از الگوهای طراحیهای ساختاری (creational design patterns) اشتباه گرفته میشوند. در بیشتر موارد، یک کارخانه ساده مرحلهای واسطهای برای معرفی الگوی طراحی متد کارخانه (Factory Method) یا الگوی کارخانه انتزاعی (Abstract Factory) است.
یک کارخانه ساده معمولاً با یک متد در یک کلاس پیادهسازی میشود. با گذشت زمان، این متد ممکن است بیش از حد بزرگ شود، بنابراین ممکن است تصمیم بگیریم بخشهایی از متد را به کلاسهای فرعی (subclasses) منتقل کنیم. وقتی این کار را چندین بار انجام دهیم، کل ساختار به تدریج به الگوی کلاسیک متد کارخانه (Factory Method Pattern) تبدیل میشود.
در اینجا مثالی از یک کارخانه ساده آورده شده است:
public class UserFactory
{
public static IUser Create(UserType type)
{
switch(type)
{
case type.User:
return new User();
case type.Customer:
return new Customer();
case type.Admin:
return new Admin();
default:
throw new ArgumentException($"Wrong user type {type}");
}
}
}
الگوی طراحی Factory Method (متد کارخانه)
الگوی طراحی متد کارخانه یک الگوی طراحی ساختاری (creational design pattern) است که یک رابط (interface) برای ایجاد اشیاء ارائه میدهد اما به کلاسهای فرعی (subclasses) اجازه میدهد نوع شیئی که قرار است ایجاد شود را تغییر دهند.
اگر در کلاس والد (superclass) یک متد ایجاد (creation method) داشته باشید و چندین کلاس فرعی که آن را گسترش میدهند، احتمالاً با یک متد کارخانه (Factory Method) سر و کار دارید.
public abstract class Department
{
public abstract Employee CreateEmployee(string id);
public void Fire(string id)
{
var employee = CreateEmployee(id);
employee.PaySalary();
employee.Dismiss();
}
}
public class ITDepartment : Department
{
public Employee CreateEmployee(string id)
{
return new Programmer(id);
}
}
public class AccountingDepartment : Department
{
public Employee CreateEmployee(string id)
{
return new Accountant(id);
}
}
الگوی کارخانه انتزاعی
الگوی کارخانه انتزاعی (Abstract Factory) یک الگوی طراحی خلاقانه است که امکان تولید خانوادههایی از اشیای مرتبط یا وابسته را فراهم میکند، بدون اینکه به کلاسهای مشخص آنها نیاز باشد.
برای مثال، کلاسهای حملونقل (Transport
)، موتور (Engine
) و کنترلها (Controls
). ممکن است چندین نوع مختلف وجود داشته باشد، مانند خودرو (Car
)، موتور احتراق داخلی (CombustionEngine
) و فرمان (Wheel
) یا کشتی (Ship
)، موتور جت آبی (WaterjetEngine
) و سکان (Helm
).
اگر شما با خانوادههای محصولات کار نمیکنید، نیازی به استفاده از کارخانه انتزاعی ندارید.
بسیاری از افراد الگوی کارخانه انتزاعی را با یک کلاس کارخانه ساده که به صورت abstract
تعریف شده است اشتباه میگیرند. از این اشتباه اجتناب کنید.
مزایا و معایب الگوی متد کارخانه
مزایا
- از ایجاد وابستگی شدید بین سازنده و محصولات مشخص جلوگیری میکنیم.
- میتوانیم کد مربوط به ایجاد محصول را به یک مکان در برنامه منتقل کنیم که نگهداری کد را آسانتر کرده و اصل مسئولیت واحد (Single Responsibility Principle) را رعایت کنیم.
- میتوانیم انواع جدیدی از محصولات را بدون تغییر در کد مشتری موجود به برنامه معرفی کنیم و بدین ترتیب اصل باز-بسته (Open/Closed Principle) را رعایت کنیم.
معایب
- ممکن است کد پیچیدهتر شود، زیرا باید زیرکلاسهای زیادی را برای پیادهسازی این الگو معرفی کنیم. بهترین حالت زمانی است که این الگو را در یک سلسلهمراتب موجود از کلاسهای سازنده معرفی کنیم.
ارتباط با الگوهای دیگر
- بسیاری از طراحیها با استفاده از متد کارخانه آغاز میشوند و به تدریج به سمت الگوهای کارخانه انتزاعی، نمونهسازی (Prototype) یا سازنده (Builder) تکامل مییابند.
- کلاسهای کارخانه انتزاعی اغلب بر اساس مجموعهای از متدهای کارخانه بنا شدهاند.
- متد کارخانه یک تخصصیسازی از الگوی متد قالب (Template Method) است. در عین حال، متد کارخانه ممکن است به عنوان یک گام در یک متد قالبی بزرگتر عمل کند.
نکات پایانی
در این مقاله، ما به بررسی الگوی متد کارخانه، زمان استفاده از آن و مزایا و معایب این الگوی طراحی پرداختیم. همچنین انواع مختلف کارخانهها و نحوه ارتباط الگوی متد کارخانه با سایر الگوهای طراحی کلاسیک را بررسی کردیم.
الگوی طراحی متد کارخانه به ما این امکان را میدهد که نوع شیء ایجاد شده را به زیرکلاسها واگذار کنیم.
این الگو به ما اجازه میدهد که یک رابط تعریف کنیم که از طریق آن، مشتری شیء را ایجاد کند. هدف اصلی آن مخفی کردن منطق ایجاد شیء از مشتری است. همچنین این الگو با نام سازنده مجازی (Virtual Constructor) شناخته میشود.
شایان ذکر است که الگوی متد کارخانه، مانند سایر الگوهای طراحی ارائه شده توسط Gang of Four، راهحل نهایی یا درمان همهچیز نیست. این مهندسان هستند که باید تصمیم بگیرند چه زمانی از یک الگوی خاص استفاده کنند. در نهایت، این الگوها زمانی مفید هستند که به عنوان ابزار دقیق استفاده شوند، نه یک چکش سنگین.