متد Factory یک الگوی طراحی سازنده است که یک رابط کاربری را برای ایجاد اشیاء بدون مشخص کردن کلاسهای واقعی آنها فراهم می کند. این الگو، متدی را تعریف می کند که می توانیم از آن برای ایجاد یک شی به جای استفاده از سازنده آن شئ استفاده کنیم. نکته مهم این است که subclasse ها می توانند این متد را override کنند و اشیایی از انواع مختلف ایجاد کنند.
در این مقاله، میخواهیم به شما نشان دهیم که چطور یک الگوی طراحی متد Factory را پیاده سازی کنید. علاوه بر این، میخواهیم یاد بگیریم که چطور از تکنیک refactoring متد Factory برای مخفی کردن سازنده و استفاده از متد خود برای افشاسازی آن استفاده کنیم.
این مقاله، بخشی از مجموعه آموزشی زیر است:
- الگوی طراحی Builder و Fluent Builder
- اینترفیس Fluent Builder به همراه Generic بازگشتی
- Facated Builder
- متد Factory
- Singleton
- Adapter
- Composite
- Decorator
- Command
- Strategy
- Facade
ما با یک پیاده سازی ساده از Factory شروع می کنیم و به تدریج آن را بهبود میدهیم تا به یک Factory پرکاربرد و readable برای اشیاء خود برسیم.
برای مشاهده لیست کامل مقالات این مجموعه آموزشی، الگوهای طراحی #C را بررسی کنید.
این مقاله، به قسمتهای زیر تقسیم میشود:
پیاده سازی متد Factory
برای پیاده سازی الگوی متد Factory، ما قصد داریم یک برنامه ساده تهویه مطبوع ایجاد کنیم. برنامه ما، یک ورودی را از کاربر میگیرد و بر اساس آن ورودی، اکشن مورد نظر (سرد کردن یا گرم کردن اتاق) trigger خواهد شد. پس با یک اینترفیس شروع میکنیم:
1 2 3 4 |
public interface IAirConditioner { void Operate(); } |
حالا کلاسهای واقعی را برای پیاده سازی این اینترفیس نیاز داریم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class CoolingManager : IAirConditioner { private readonly double _temperature; public CoolingManager(double temperature) { _temperature = temperature; } public void Operate() { Console.WriteLine($"Cooling the room to the required temperature of {_temperature} degrees"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class WarmingManager : IAirConditioner { private readonly double _temperature; public WarmingManager(double temperature) { _temperature = temperature; } public void Operate() { Console.WriteLine($"Warming the room to the required temperature of {_temperature} degrees."); } } |
بسیار خب. ما زیرساخت خود را آماده کردیم. حالا باید یک سازنده Factory برای این اشیاء ایجاد کنیم.
کلاسهای Factory
ما قصد داریم با کلاس AirConditionerFactory
abstract شروع کنیم:
1 2 3 4 |
public abstract class AirConditionerFactory { public abstract IAirConditioner Create(double temperature); } |
این کلاس abstract، یک رابط برای ایجاد شیء در کلاسهای مشتق شده ارائه میدهد. با این اوصاف، کلاسهای creator واقعی خود را پیاده سازی میکنیم:
1 2 3 4 |
public class CoolingFactory : AirConditionerFactory { public override IAirConditioner Create(double temperature) => new CoolingManager(temperature); } |
1 2 3 4 |
public class WarmingFactory : AirConditionerFactory { public override IAirConditioner Create(double temperature) => new WarmingManager(temperature); } |
عالی. حالا ما آماده ایم تا از متدهای Factory خود استفاده کنیم. در خیلی از مثالها، ما می توانیم عبارت switch را ببینیم که از طریق ورودی کاربر switche میکند و کلاس Factory مورد نیاز را انتخاب می کند.
این رویکرد درست کار میکند.
اما تصور کنیم که تعداد زیادی کلاسهای factory داشته باشیم که در پروژه های بزرگ، چیز رایجی هم است که در نتیجه منجربه به وجود آمدن یک عبارت switch case خیلی بزرگ میشود که کاملا ناخوانا است. بنابراین ما قصد داریم از رویکرد دیگری استفاده کنیم.
Factory Execution
با یک شمارنده ساده برای تعریف اقدامات تهویه مطبوع شروع میکنیم:
1 2 3 4 5 |
public enum Actions { Cooling, Warming } |
در ادامه، ما قصد داریم کلاس AirConditioner
را ایجاد کنیم که در آن، کاربر میتواند نوع action را مشخص کرده و factory مناسب را اجرا کند. factory های واقعی ما از کلاس abstract مورد نظر ارث بری میکنند و ما قصد داریم از این ساختار، در پیاده سازی بعدی خود استفاده کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class AirConditioner { private readonly Dictionary<Actions, AirConditionerFactory> _factories; public AirConditioner() { _factories = new Dictionary<Actions, AirConditionerFactory> { { Actions.Cooling, new CoolingFactory() }, { Actions.Warming, new WarmingFactory() } }; } } |
این، روش بهتری برای اجرای factory ما نسبت به استفاده از دستور switch-case است. اما ما می توانیم آن را به شیوه ای پویاتر نیز انجام دهیم. طوریکه مجبور نباشیم به صورت دستی، action و سازنده factory را برای هر action اضافه کنیم. در نتیجه، برای رفع این مسئله، از reflection در پروژه خود استفاده میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class AirConditioner { private readonly Dictionary<Actions, AirConditionerFactory> _factories; public AirConditioner() { _factories = new Dictionary<Actions, AirConditionerFactory>(); foreach (Actions action in Enum.GetValues(typeof(Actions))) { var factory = (AirConditionerFactory)Activator.CreateInstance(Type.GetType("FactoryMethod." + Enum.GetName(typeof(Actions), action) + "Factory")); _factories.Add(action, factory); } } } |
حالا چه رویکرد اولی را انتخاب کنیم چه این دومی، نتیجه یکسان خواهد بود:
یک چیز دیگر را باید به این کلاس اضافه کنیم و آن متدی است که سازنده مناسب را اجرا میکند:
1 2 3 4 5 6 |
public class AirConditioner { //previous constructor code public IAirConditioner ExecuteCreation(Actions action, double temperature) =>_factories[action].Create(temperature); } |
حالا، وقت آن است که یک فراخوانی از یک کلاینت ایجاد کنیم. در یک پروژه واقعی، ما مطمئناً ابتدا دمای فعلی را بررسی می کنیم و سپس فقط یک factory خواهیم داشت تا تصمیم بگیرد که آیا باید آن دما را پایین بیاوریم یا بالاتر ببریم. اما به دلیل ساده نگه داشتن مسئله، ما فقط می خواهیم یک فراخوانی ساده به سمت کلاس AirConditioner خود برقرار کنیم:
1 2 3 4 5 6 7 8 |
class Program { static void Main(string[] args) { var factory = new AirConditioner().ExecuteCreation(Actions.Cooling, 22.5); factory.Operate(); } } |
نتیجه همانی است که انتظار میرفت:
استفاده از تکنیک Refactoring متد Factory
ما میتوانیم از متد Factory برای جایگزینی سازنده خود در حین ایجاد یک شئ استفاده کنیم. اگر سازنده ما، شامل مقدار زیادی کد باشد، بهتر است آن را با متد factory جایگزین کنیم. علاوه بر این، میتوانیم چند متد factory با نامهای با مسما و همچنین نامهای پارامتری داشته باشیم که جایگزین یک سازنده واحد میشود.
این خوانایی کد را به مقدار قابل توجهی افزایش میدهد.
در نهایت، آن به ما کمک میکند تا یک سینتکس زنجیروارانه را نیز پیاده سازی کنیم.
پس کلاس AirConditioner
را با متد factory تغییر میدهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class AirConditioner { private readonly Dictionary<Actions, AirConditionerFactory> _factories; private AirConditioner() { _factories = new Dictionary<Actions, AirConditionerFactory>(); foreach (Actions action in Enum.GetValues(typeof(Actions))) { var factory = (AirConditionerFactory)Activator.CreateInstance(Type.GetType("FactoryMethod." + Enum.GetName(typeof(Actions), action) + "Factory")); _factories.Add(action, factory); } } public static AirConditioner InitializeFactories() => new AirConditioner(); public IAirConditioner ExecuteCreation(Actions action, double temperature) =>_factories[action].Create(temperature); } |
نحوه فراخوانی کلاینت ما نیز باید تغییر کند:
1 2 3 4 5 6 7 8 9 10 |
class Program { static void Main(string[] args) { AirConditioner .InitializeFactories() .ExecuteCreation(Actions.Cooling, 22.5) .Operate(); } } |
بسیار عالی. نتیجه باید مانند قبل باشد با این تفاوت که اینبار از تکنیک Refactoring متد Factory استفاده کردیم.
نتیجه گیری
با خواندن این مقاله موارد زیر را یاد گرفتیم:
- چطور الگوی طراحی متد Factory را به داخل اپلیکیشن خود پیاده سازی کنیم.
- چند راه جایگزین برای دستور switch-case با استفاده از یک dictionary یا reflection
- نحوه refactor کردن کد خود با استفاده از تکنیک Refactoring متد Factory