زمان تخمینی مطالعه: 13 دقیقه
الگوهای طراحی Creational دستهای از Design Pattern های نرم افزار هستند که با مکانیسمهای ایجاد شی سروکار دارند. آنها راهها و تکنیکهای مختلفی را برای ایجاد اشیاء به روشی ارائه میدهند که انعطاف پذیر و قابل استفاده مجدد بوده و بتواند اصول طراحی بهتر را ترویج کند. در ادامه به تشریح متد Factory از این الگو میپردازیم.
متد Factory
متد Factory که با نام سازنده مجازی نیز شناخته میشود، یک الگوی طراحی ایجادی(Creational) است که یک رابط(Interface) برای ایجاد اشیاء در یک سوپرکلاس فراهم میکند، اما به زیر کلاسها اجازه میدهد تا نوع اشیایی که ایجاد میشوند را تغییر دهند.
بیان مسئله: تصور کنید که در حال ایجاد یک برنامه کامپیوتری برای مدیریت حمل و نقل(Logistics) هستید. اولین نسخه برنامه شما فقط قادر است تا حمل و نقل با کامیون را انجام دهد، بنابراین بخش عمده کد شما در کلاس Truck قرار دارد. پس از مدتی، برنامه شما بسیار محبوب و پر استفاده میشود و هر روز دهها درخواست از شرکتهای حمل و نقل دریایی برای گنجاندن روش حمل و نقل و تدارکات دریایی در برنامه دریافت می کنید.
این یک خبر عالی و امیدوار کننده است ولی مشکل این است که چگونه باید کد نرم افزار نوشته شده را برای تطبیق با فرآیندهای جدید تغییر دهیم؟ در حال حاضر، بیشتر کد نوشته شده در سیستم فعلی شما به کلاس Truck وابسته شده است. افزودن کلاس Ships به برنامه مستلزم ایجاد تغییراتی در کل پایگاه کد برنامه شما است. علاوه بر این، اگر بعداً تصمیم گرفتید نوع دیگری از حمل و نقل را به برنامه خود اضافه کنید، احتمالاً باید همه این تغییرات را دوباره در آن زمان هم انجام دهید. در نتیجه، و با در نظر گرفتن این موارد شما کد بسیار آشفتهای خواهید داشت، که مملو از شرایطی است که رفتار برنامه را بسته به کلاس اشیاء حملونقل تغییر میدهد. چاره چیست؟
الگوی طراحی Factory Method پیشنهاد میکند که فراخوانیهای ساخت مستقیم شی (با استفاده از اپراتور جدید) را با فراخوانی به روش Factory خاص جایگزین کنید. شاید این موضوع باعث ایجاد نگرانی شود ولی نگران نباشید: اشیاء هنوز هم از طریق اپراتور جدید ایجاد میشوند، اما از روش Factory فراخوانی میشوند. اشیایی که با روش Factory بازگردانده میشوند اغلب به عنوان محصولات شناخته میشوند.
در نگاه اول، این تغییر ممکن است بی معنی به نظر برسد. در واقع ما فقط فراخوان سازنده(Constructor) را از یک قسمت برنامه به قسمت دیگر منتقل کردیم. با این حال، این را در نظر بگیرید که اکنون میتوانید روش Factory را در یک زیر کلاس بازنویسی(Override) کنید و کلاس محصولات ایجاد شده توسط آن متد را تغییر دهید. با این حال یک محدودیت جزئی باقی میماند و آن این است که کلاسهای فرعی ممکن است انواع مختلفی از محصولات را تنها در صورتی برگردانند که این محصولات دارای یک کلاس پایه یا اینترفیس مشترک باشند. همچنین متد Factory در کلاس پایه باید نوع بازگشتی خود را به عنوان این اینترفیس اعلام کند.
برای مثال، هر دو کلاس Truck و Ship باید اینترفیس Transport را پیادهسازی کنند، که متدی به نام deliver را اعلام میکند. هر کلاس این روش را به صورتی متفاوت اجرا میکند: کامیونها محموله را از طریق زمینی و کشتیها محموله را از طریق دریا تحویل میدهند. متد Factory در کلاس RoadLogistics اشیاء کامیون را برمیگرداند، در حالی که متد Factory در کلاس SeaLogistics کشتیها را برمیگرداند.
کدی که از متد Factory استفاده میکند (که اغلب کد مشتری(Client Code) نامیده میشود) تفاوتی بین محصولات واقعی بازگردانده شده توسط زیر کلاسهای مختلف نمیبیند. مشتری با تمام محصولات به عنوان Transport انتزاعی رفتار میکند. مشتری میداند که همه اشیاء حمل و نقل قرار است متد deliver را داشته باشند، اما نحوه عملکرد دقیق آن برای مشتری مهم نیست.
- گام 1: Product اینترفیس را اعلام میکند که برای همه اشیایی که میتوانند توسط سازنده و زیر کلاسهای آن تولید شوند مشترک است.
- گام 2: Concrete Products پیادهسازیهای مختلف اینترفیس Product هستند.
- گام 3: کلاس Creator متد Factory را اعلام میکند که اشیاء محصول جدید را بر میگرداند. مطابقت نوع بازگشت این روش با رابط محصول موضوع مهمی است. در حالت کلی میتوان متد Factory را بهعنوان abstract تعریف کرد تا همه زیر کلاسها مجبور شوند نسخههای خود را از این متد پیادهسازی کنند. به عنوان یک جایگزین، روش کارخانه پایه می تواند برخی از انواع محصولات پیش فرض را برگرداند.
- گام 4:Concrete Creators متد Factory پایه را نادیده میگیرد بنابراین نوع متفاوتی از محصول را بر میگرداند.
شبه کد(Pseudocode)
این مثال نشان میدهد که چگونه میتوان از متد Factory برای ایجاد عناصر رابط کاربری(UI) مستقل از پلتفرم بدون وابسته کردن کد مشتری با کلاسهای UI مشخص استفاده کرد.
کلاس پایه Dialog از عناصر UI مختلف برای ارائه پنجره خود استفاده میکند. در محیط سیستم عاملهای مختلف، این عناصر ممکن است کمی متفاوت به نظر برسند، اما همچنان باید به طور مداوم رفتار کنند. یک دکمه در ویندوز هنوز یک دکمه در لینوکس است. هنگامی که متد Factory وارد عمل میشود، دیگر نیازی به بازنویسی منطق کلاس Dialog برای هر سیستم عامل به طور مجزا ندارید. اگر یک متد Factory را تعریف کنیم که دکمههایی را در داخل کلاس پایه Dialog تولید میکند، بعداً میتوانیم یک کلاس فرعی ایجاد کنیم که دکمههای سَبکِ ویندوز را از متد Factory بر میگرداند. سپس کلاسهای فرعی اکثر کدهای خود را از کلاس پایه به ارث میبرد، اما به لطف متد Factory، میتواند دکمههای شبیه به ویندوز را روی صفحه نمایش دهد. برای اینکه این الگو به درستی کار کند، کلاس پایه Dialog باید با دکمههای انتزاعی کار کند: یک کلاس پایه یا یک اینترفیس که همه دکمههای Concrete از آن پیروی میکنند. به این ترتیب، کد Dialog با هر نوع دکمهای که کار میکند، کاربردی باقی میماند. البته، میتوانید این رویکرد را در سایر عناصر رابط کاربری نیز اعمال کنید. با این حال، با هر متد Factory جدیدی که به Dialog اضافه میکنید، به الگوی Abstract Factory نزدیک می شوید که بعداً در مورد این الگو صحبت خواهیم کرد.
// The creator class declares the factory method that must
// return an object of a product class. The creator's subclasses
// usually provide the implementation of this method.
class Dialog is
// The creator may also provide some default implementation
// of the factory method.
abstract method createButton():Button
// Note that, despite its name, the creator's primary
// responsibility isn't creating products. It usually
// contains some core business logic that relies on product
// objects returned by the factory method. Subclasses can
// indirectly change that business logic by overriding the
// factory method and returning a different type of product
// from it.
method render() is
// Call the factory method to create a product object.
Button okButton = createButton()
// Now use the product.
okButton.onClick(closeDialog)
okButton.render()
// Concrete creators override the factory method to change the
// resulting product's type.
class WindowsDialog extends Dialog is
method createButton():Button is
return new WindowsButton()
class WebDialog extends Dialog is
method createButton():Button is
return new HTMLButton()
// The product interface declares the operations that all
// concrete products must implement.
interface Button is
method render()
method onClick(f)
// Concrete products provide various implementations of the
// product interface.
class WindowsButton implements Button is
method render(a, b) is
// Render a button in Windows style.
method onClick(f) is
// Bind a native OS click event.
class HTMLButton implements Button is
method render(a, b) is
// Return an HTML representation of a button.
method onClick(f) is
// Bind a web browser click event.
class Application is
field dialog: Dialog
// The application picks a creator's type depending on the
// current configuration or environment settings.
method initialize() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
dialog = new WindowsDialog()
else if (config.OS == "Web") then
dialog = new WebDialog()
else
throw new Exception("Error! Unknown operating system.")
// The client code works with an instance of a concrete
// creator, albeit through its base interface. As long as
// the client keeps working with the creator via the base
// interface, you can pass it any creator's subclass.
method main() is
this.initialize()
dialog.render()
قابلیتها و کاربردها
- زمانی از متد Factory استفاده کنید که از قبل انواع و وابستگیهای دقیق اشیایی که کد شما باید با آنها کار کند را نمیدانید. متد Factory کد ساخت محصول را از کدی که در واقع از محصول استفاده میکند جدا میکند. بنابراین، گسترش کد ساخت محصول به طور مستقل از بقیه کد آسانتر است. به عنوان مثال، برای افزودن یک نوع محصول جدید به برنامه، فقط باید یک زیر کلاس سازنده جدید ایجاد کنید و متد Factory را در آن بازنویسی(Override) کنید.
- زمانی که میخواهید به کاربران کتابخانه یا چارچوب(Framework) خود راهی برای گسترش اجزای داخلی آن ارائه دهید، از متد Factory استفاده کنید. وراثت احتمالاً سادهترین راه برای گسترش رفتار پیش فرض یک کتابخانه یا چارچوب است. اما فریم ورک چگونه تشخیص میدهد که زیر کلاس شما باید به جای یک جزء استاندارد استفاده شود؟ راه حل این است که کدی را که اجزاء را در سرتاسر فریم ورک میسازد به یک متد Factory واحد کاهش دهیم و به هر کسی اجازه دهیم این روش را علاوه بر گسترش خود مؤلفه بازنویسی(Override) کند. تصور کنید که برنامهای را با استفاده از یک چارچوب UI منبع باز مینویسید. برنامه شما باید دکمههای گرد داشته باشد، اما چارچوب فقط دکمههای مربعی را ارائه میدهد. شما کلاس Button استاندارد را با یک زیر کلاس با جدید RoundButton گسترش میدهید. اما اکنون باید به کلاس اصلی UIFramework بگویید که از زیر کلاس دکمه جدید به جای یک کلاس پیش فرض استفاده کند. برای رسیدن به این هدف، یک زیر کلاس UIWithRoundButtons از یک کلاس چارچوب پایه ایجاد میکنید و متد createButton آن را بازنویسی(Override) میکنید. در حالی که این متد اشیاء Button را در کلاس پایه بر میگرداند، شما باعث میشوید که زیر کلاس خود اشیاء RoundButton را برگرداند. حالا به جای UIFramework از کلاس UIWithRoundButtons استفاده کنید.
- زمانی که میخواهید منابع سیستم را با استفاده مجدد از اشیاء موجود به جای بازسازی هر بار، ذخیره کنید، از روش Factory استفاده کنید. شما اغلب این نیاز را هنگام برخورد با اشیاء بزرگ و پر مصرف مانند اتصالات پایگاه داده، سیستمهای فایل و منابع شبکه تجربه میکنید. بیایید به این فکر کنیم که برای استفاده مجدد از یک شی موجود چه کاری باید انجام شود:
- ابتدا باید مقداری فضای ذخیره سازی ایجاد کنید تا تمام اشیاء ایجاد شده را ردیابی کنید.
- هنگامی که شخصی یک شی را درخواست میکند، برنامه باید به دنبال یک شی آزاد در داخل آن استخر باشد.
- … و سپس آن را به کد مشتری برگردانید.
- اگر هیچ شی آزاد وجود نداشته باشد، برنامه باید یک مورد جدید ایجاد کند (و آن را به استخر اضافه کند).
احتمالا واضحترین و راحتترین مکانی که میتوان این کد را در آن قرار داد، سازنده کلاسی است که از اشیاء آن میخواهیم دوباره استفاده کنیم. با این حال، سازنده همیشه باید اشیاء جدید را طبق تعریف برگرداند. نمیتواند نمونههای موجود را برگرداند. بنابراین، شما باید یک روش منظم داشته باشید که قادر به ایجاد اشیاء جدید و همچنین استفاده مجدد از موارد موجود باشد. این به نظر بسیار شبیه یک متد Factory است.
نحوه پیادهسازی
- کاری کنید که همه محصولات از یک اینترفیس استفاده کنند. این اینترفیس باید متدهایی را که در هر محصول معنادار هستند را بیان(declare) کند.
- یک متد خالی کارخانه داخل کلاس creator اضافه کنید. نوع برگشت روش باید با رابط محصول مشترک مطابقت داشته باشد.
- در کد سازنده، همه ارجاعات به سازندههای محصول را بیابید. یک به یک آنها را با فراخوانی با متد Factory جایگزین کنید، در حالی که کد ایجاد محصول را با متد Factory استخراج کنید.
- ممکن است لازم باشد یک پارامتر موقت به روش کارخانه اضافه کنید تا نوع محصول برگشتی را کنترل کنید.
- در این مرحله، کد متد Factory ممکن است بسیار زشت به نظر برسد. ممکن است یک دستور سوئیچ بزرگ داشته باشد که انتخاب میکند کدام کلاس محصول را نمونهسازی کند.
- اکنون، مجموعهای از زیر کلاسهای سازنده برای هر نوع محصول فهرست شده در متد Factory ایجاد کنید. متد Factory را در زیر کلاسها بازنویسی(Override) کنید و بیتهای کد ساخت مناسب را از روش پایه استخراج کنید.
- اگر انواع محصول خیلی زیاد است و ایجاد زیر کلاس برای همه آنها منطقی نیست، میتوانید پارامتر کنترل را از کلاس پایه در زیر کلاسها دوباره استفاده کنید. به عنوان مثال، تصور کنید که شما سلسله مراتب کلاسهای زیر را دارید: کلاس پایه Mail با چند زیر کلاس: AirMail و GroundMail. کلاسهای Transport شامل Plain، ،Train و Truck هستند. در حالی که کلاس AirMail فقط از اشیاء Plane استفاده میکند، GroundMail ممکن است با هر دو شی Truck و Train کار کند. همچنین شما میتوانید یک زیر کلاس جدید (مثلا TrainMail) برای رسیدگی به هر دو مورد ایجاد کنید، اما گزینه دیگری هم وجود دارد. کد کلاینت میتواند یک آرگومان را به متد Factory کلاس GroundMail ارسال کند تا بتواند محصولی که میخواهد دریافت کند را کنترل کند.
مزایا و معایب متد Factory
این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:
– مزایا
- اجتناب از وابستگی بین مولد و محصولات Concrete
- اصل مسئولیت واحد(Single Responsibility Principle): میتوانید کد ایجاد محصول را به یک مکان در برنامه منتقل کنید تا پشتیبانی از کد آسانتر شود.
- اصل باز/بسته(Open/Closed Principle): میتوانید انواع جدیدی از محصولات را بدون شکستن کد مشتری موجود به برنامه معرفی کنید.
– معایب
با استفاده از این الگو ممکن است کد پیچیدهتر شود زیرا برای پیادهسازی الگو باید زیر کلاسهای جدید زیادی معرفی کنید. بهترین حالت زمانی است که شما الگو را در یک سلسله مراتب موجود از کلاسهای سازنده معرفی میکنید.
ارتباط با الگوهای دیگر
- بسیاری از الگوها با استفاده از متد Factory (کمتر پیچیدهتر و قابل تنظیمتر از طریق زیر کلاسها) شروع میشوند و کم کم به سمت الگوهای دیگر مانند Abstract Factory، Prototype یا Builder (انعطافپذیرتر، اما پیچیدهتر) تکامل مییابند.
- کلاسهای Abstract Factory اغلب بر اساس مجموعهای از متدهای Factory هستند، اما شما همچنین میتوانید از الگوی Prototype برای ترکیب متدهای این کلاسها استفاده کنید.
- میتوانید از متد Factory همراه با الگوی Iterator استفاده کنید تا به زیر کلاسهای مجموعه اجازه دهید انواع مختلفی از تکرارکنندهها را که با مجموعهها سازگار هستند، برگردانند.
- الگوی Prototype مبتنی بر وراثت نیست، بنابراین معایب آن را ندارد. از سوی دیگر، Prototype به یک مقداردهی اولیه پیچیده از شی کلون شده نیاز دارد. متد Factory بر اساس وراثت عمل میکند اما نیازی به مرحله مقدار دهی اولیه ندارد.
- متد Factory یک نسخه تخصصی از متد Template است. همچنین یک متد Factory ممکن است به عنوان مرحلهای در داخل یک متد Template بزرگ عمل کند.