Design Patterns – Factory Pattern

در مطلب قبلی، به این پرداختیم که اصلا چرا باید سراغ الگوهای طراحی رفت، وبه بررسی Strategy Pattern پرداختیم. در این مطلب، الگوی Factory رو مورد بحث قرار میدیم، و البته در ابتدا یک سری مسائل تئوری رو مطرح می کنیم.

بابک فخریلو

تعریف

نقل و قول رایج در تعریف این الگو بدین شکل است: «الگوی Factory یک واسط برای ایجاد یک شی تعریف می کند، اما به subclass ها (کلاس های مشتق) اجازه می دهد تا در ایجاد نمونه سازی از کلاس خودشان تصمیم بگیرند.» اما همان طور که خواهید دید، در عمل، کاربرد بیشتری از آن چه در تعریف اش آماده، ارئه می شود.

در حقیقت، برنامه نویسی با مد نظر قرار دادن Interface، به ما اجازه می  دهد تا صرفا زمانی که نیاز است، پیاده سازی دلخواه خود را به کار ببندیم.

اما در عمل

در دنیای واقعی، نیازمندی هایی که یک نرم افزار را می سازند، به ندرت ایستا هستند، و در حال تغییر اند. شاید framework خوبی که برای یک سیستم مشخص طراحی شده، از پس کارهایی که هیچ وقت برای شان تعریفی نداشته، بر نیاید. بهتر است که قبل از به هم ریختن کد برنامه به خاطر افزودن ویژگی های جدید، آینده نگری به خرج دهیم. برای این کار، کد نویسی با رعایت اصل polymorphism می تواند در برابر نیاز های آینده ما را مصون کند، و اینجاست که Factory Pattern خودش را نشان می دهد.

مساله

فرض کنید همان شرکتی که در مطلب قبلی مثال زدیم، با مشکلات اقتصادی مواجه شده است. رئیس ما پس از صرف چند هفته برای حل این مشکل، به ایده ای رسیده است. ما باید به طور موقت فروش بیسکویت را در وب سایت خودمان متوقف کنیم و به جای آن لباس سگ بفروشیم! او از ما می خواهد سایت را طوری تغییر دهیم که بتوان محصول جدید را به فروش رساند.

تلاش اول برای حل مساله

خوب در زیر  کد برنامه را داریم که مسئول کنترل نمایش محصول در وب سایت است:

protected void Page_Load(object sender, EventArgs e)

{

lblTitle.Text = "Biscuits For Sale";

lblTitle.BackColor = Color.LightCoral;

IEnumerable<string> biscuits = new List<string>() {

"Hob Nob",

"Custard Creams",

"Chocolate Digestives" };

lstProducts.DataSource = biscuits;

lstProducts.DataBind();

IEnumerable<string> shippingLocations = new List<string>() {

"England",

"Australia",

"Kazakhstan" };

lstShipping.DataSource = shippingLocations;

lstShipping.DataBind();

}

کد بالا، مربوط به یک وب سایت نوشته شده با استفاده از تکنولوژی ASP.NET است. کمی در مورد کد توضیح بدهم. ما دو کنترل نمایش فهرست داده ها در صفحه  ی وب خود داریم که یکی lstProducts نام دارد و فهرستی از محصولات را نشان می دهد، و دیگری lstShipping که فهرست مقصد های عرضه محصول را در خود دارد. داده های این دو فهرست، توسط biscuits و shippingLocations تامین می شوند.

رئیس به ما گفته که محصول جدید می تواند رنگ و عنوان جدیدی داشته باشد، پس ما راه حلی را ارائه می دهیم که نباید بدهیم:

protected void Page_Load(object sender, EventArgs e)

{

string product = "clothing";

if (product == "biscuit")

{

lblTitle.Text = "Biscuits For Sale";

lblTitle.BackColor = Color.LightCoral;

IEnumerable<string> biscuits = new List<string>() {

"Hob Nob",

"Custard Creams",

"Chocolate Digestives" };

lstProducts.DataSource = biscuits;

}

else if (product == "clothing")

{

lblTitle.Text = "Dog Clothes For Sale";

lblTitle.BackColor = Color.LightGreen;

IEnumerable<string> clothes = new List<string>() {

"Branded cap",

"Paw warmers",

"Furry pants" };

lstProducts.DataSource = clothes;

}

lstProducts.DataBind();

IEnumerable<string> shippingLocations = new List<string>() {

"England",

"Australia",

"Kazakhstan" };

lstShipping.DataSource = shippingLocations;

lstShipping.DataBind();

}

گرچه این کد به درستی کار می کند، و ما می توانیم خیلی راحت با تغییر دادن رشته ی product، بین محصولات سوئیچ کنیم. اما تصور کنید که رئیس ما بخواهد هفته ی بعد محصول دیگری را بفروشد! باز هم رشد دستورات else/if. شاید اصلا بخواهد رنگ پس زمینه ی متن، با رنگ محصول یکی باشد! چقدر باید به نوشتن else/if ادامه دهیم؟!

Factory Pattern: تلاش اول

همان اصل مهمی که در مطلب قبل هم اشاره کردیم، کدی که تغییر می کند را کپسوله کنید، و این یعنی باید از Design Pattern استفاده کرد. خوب در مساله ما، چه چیزی تغییر می کند؟ عنوان، رنگ پس زمینه و محصولات. این ها را کپسوله می کنیم. چطور؟ الان زیاد مهم نیست، اما اول از دوست قدیمی مان، یعنی Interface، کمک می گیریم:

public interface IProduct

{

string Title{ get; }

Color BackgroundColour{ get; }

IEnumerable<string> Products { get; }

}

ممکن است مقصد عرضه ی کالا هم در آینده تغییر کند، اما فعلا کاری به آن نداریم.

حالا می توانیم داده های در حال تغییر خود را به subclass ها ببریم، و این subclass ها را وادار به پیادی سازی واسط کنیم:

public class Biscuits : IProduct

{

#region IProduct Members

public string Title

{

get { return "Biscuits For Sale"; }

}

public Color BackgroundColour

{

get { return Color.LightCoral; }

}

public IEnumerable<string> Products

{

get

{

return new List<string>() {

"Hob Nob",

"Custard Creams",

"Chocolate Digestives" };

}

}

#endregion

}

public class Clothing : IProduct

{

#region IProduct Members

public string Title

{

get { return "Dog Clothes For Sale"; }

}

public Color BackgroundColour

{

get { return Color.LightGreen; }

}

public IEnumerable<string> Products

{

get

{

return new List<string>() {

"Branded cap",

"Paw warmers",

"Furry pants" };

}

}

#endregion

}

حالا کلاس کنترلی که در ابتدا تعریف کرده بودیم، ساده تر می شود، و صرفا وابسته به یک واسط به نام IProduct می شود:

protected void Page_Load(object sender, EventArgs e)

{

IProduct product = GetProduct("clothing");

lblTitle.Text = product.Title;

lblTitle.BackColor = product.BackgroundColour;

IEnumerable<string> products = product.Products;

lstProducts.DataSource = products;

lstProducts.DataBind();

IEnumerable<string> shippingLocations = new List<string>() {

"England",

"Australia",

"Kazakhstan" };

lstShipping.DataSource = shippingLocations;

lstShipping.DataBind();

}

کد کلیدی در بالا، Product product = GetProduct(«clothing”)» است. ما تمامی داده های concrete را abstract کردیم و تمامی مقدار دهی های اولیه به اشیا را از  کد خود حذف کردیم. و حالا کد زیر که اصطلاحا به آن Factory Method می گویند:

public IProduct GetProduct(string type)

{

if (type == "biscuit")

{

return new Biscuits();

}

else if (type == "clothing")

{

return new Clothing();

}

else

{

// Returning a default

// or throw exception, or panic

return new Biscuits();

}

}

در داخل متد بالا، اشیایی که ساخته می شوند، نوع خاصی از واسط هستند، و در حقیقت ما  نوع concrete  از Biscuits یا Clothing یا اصلا هر محصول از نوع IProduct  را اینجا می سازیم.

خوب خیلی ها همین را پایان کار می دانند. گرچه چیزی که دیدیم یک الگوی طراحی بود و بیشتر آنچه نیازمان بود را اراضا کرد، اما با تعریفی که در ابتدای مطلب گفتیم، تفاوت دارد.

خوب حالا چرا ناکافی است؟ ما کد متغیر خود را به یک تابع برده ایم و هر زمان که محصول جدیدی بخواهد اضافه شود، یک else if جدید باید به آن اضافه کنیم. این روش کار ما را راه می  اندازد، اما یک هدف کلیدی در تولید نرم افزار reusable را نقض می کند: کد باید امکان توسعه پذیری داشته باشد و نسبت به تغییر محدود باشد. تغییر دادن کدی که درست کار می کند، همیشه با خطر از کار افتادن کد همراه است. کدی که در اینجا مطرح کردیم، قطعا نسبت به تغییر محدود نیست. فرض کنید framework خود را به یک شرکت دیگر بخواهیم بفروشیم، اما از طرفی می خواهیم از source code هم حفاظت کنیم. شرکت خریدار framework ما هیچ راهی برای افزودن یک محصول جدید ندارد، چون نمی تواند GetProduct را تغییر دهد. چاره ای نیست جز این که خودمان کد را تغییر دهیم.

هنوز  قانع نشدید؟ فکر کنید بیسکویت های که به استرالیا عرضه شده اند، مشکل دارند و باید همه ی آنها را متوقف کرد. همین کار ما را مجبور می کند تا عرضه را برای یک محصول بخصوص تغییر دهیم، و به کد زیر برسیم:

...

IEnumerable<string> shippingLocations = GetShippingLocations("biscuit");

...

public IEnumerable<string> GetShippingLocations(string type)

{

if (type == "biscuit")

{

return new List<string>() {

"England",

"Kazakhstan" };

}

else

{

// Returning a default

return new List<string>() {

"England",

"Australia",

"Kazakhstan" };

}

}

خوب بیاید ببینیم واقعا Factory Pattern چطوری در حل این مشکل به ما کمک می کند.

Factory Pattern: تلاش آخر

یک کلاس abstract یا یک interface برای تعریف کردن factory می سازیم، و بعد factory های مشتق از این واسط (یا abstract class) را پیاده سازی می کنیم، که مشخص می کند دقیقا چه اشیایی باید ساخته شوند.

کد زیر Factory مورد نیاز ماست، به همراه یک متد کمکی که مشخص می کند محصول به کدام کشور ها باید برود:

public abstract class ProductFactory

{

public abstract IProduct CreateProduct();

public virtual IEnumerable<string> ShippingLocations()

{

return new List<string> {

"England",

"Australia",

"Kazakhstan" };

}

}

پیاده سازی خیلی  ساده برای محصول clothing بدین شکل می شود:

public class ClothingFactory : ProductFactory

{

public override IProduct CreateProduct()

{

return new Clothing();

}

}

برای بیسکویت هم می توانیم چنین کاری بکنیم، اما می دانیم که برای برخی کشور ها واردات این محصول ممنوع شده، پس داریم:

public class BiscuitFactory : ProductFactory

{

public override IProduct CreateProduct()

{

return new Biscuits();

}

public override IEnumerable<string> ShippingLocations()

{

return new List<string> {

"England",

"Kazakhstan" };

}

}

حالا کد کنترلی ما به سادگی زیر می شود، و همه ی مسئولیت ها به دوش factory می افتد:

protected void Page_Load(object sender, EventArgs e)
{
ProductFactory factory = new BiscuitFactory();

IProduct product = factory.CreateProduct();

lblTitle.Text = product.Title;

lblTitle.BackColor = product.BackgroundColour;

IEnumerable<string> products = product.Products;

lstProducts.DataSource = products;

lstProducts.DataBind();

// Use helper custom function within factory

lstShipping.DataSource = factory.ShippingLocations();

lstShipping.DataBind();

}

حالا دیگر در فرآیند نگهداشت این نرم افزار، تنها کار لازم، مشتق کردن یک محصول جدید از IProduct و نیز یک factory از ProductFactory است، که محصول مورد نظر را به ما برگرداند.

مفید بود؟

خودتان قضاوت کنید، اما ما هم به چند مورد دیگر از فواید این الگو اشاره می کنیم:

  • می توانید یک factory آزمایشی ایجاد کنید که داده های تستی بر می گرداند، و وابسته به دیتابیس نیست. بدین ترتیب خیلی کارها که قبلا با هزینه ی زیادی انجام می دادید تا صحت سیستم را بررسی کنید، می توانید به طور شبیه سازی شده و با داده های غیرواقعی انجام دهید.
  • خیلی راحت می توانید حس و حال برنامه تان را تغییر دهید. می توانید چند front end برای برنامه ی تان بنویسید. از الگوی طراحی Factory برای شکل دادن به  GUI خودتان استفاده کنید تا به سادگی ظاهر برنامه تان را بتوانید تغییر دهید. حتی می توانید از این الگو برای طراحی برنامه هایی استفاده کنید که قابلیت اجرا روی فناوری های مختلفی چون winforms، webforms، WPF  را دارند.
  • می توانید یک factory class را به عنوان پارامتر به یک متد بفرستید تا رفتار آن متد را در زمان اجرا، تغییر دهید!!!

نتیجه گیری

این الگو، برای حفاظت از عملکرد داخلی کد شما بسیار عالی است. کدتان را طوری بنویسید که حتما از واسط ها استفاده کنند، و بعد کسی که قرار است از  کد شما استفاده کند، می تواند factory مورد نظرش را برای ایجاد اشیا، تعریف کند.

برگرفته از:

Design Patterns Part 2 – The Factory Pattern

دربارهٔ Persian Developer

I Love Developing applications

Posted on ژوئن 17, 2012, in توسعه نرم افزار and tagged , , . Bookmark the permalink. بیان دیدگاه.

پاسخی بگذارید

در پایین مشخصات خود را پر کنید یا برای ورود روی شمایل‌ها کلیک نمایید:

نشان‌وارهٔ وردپرس.کام

شما در حال بیان دیدگاه با حساب کاربری WordPress.com خود هستید. بیرون رفتن / تغییر دادن )

تصویر توییتر

شما در حال بیان دیدگاه با حساب کاربری Twitter خود هستید. بیرون رفتن / تغییر دادن )

عکس فیسبوک

شما در حال بیان دیدگاه با حساب کاربری Facebook خود هستید. بیرون رفتن / تغییر دادن )

عکس گوگل+

شما در حال بیان دیدگاه با حساب کاربری Google+ خود هستید. بیرون رفتن / تغییر دادن )

درحال اتصال به %s

%d وب‌نوشت‌نویس این را دوست دارند: