الگوهای برنامه نویسی
بسم الله الرحمن الرحیم
بخش پنجم
الگوهای برنامه نویسی
الگوی Factory
شاید با مفهوم (OCP (Open Close Principle آشنا باشید، OCP یکی از اصول اساسی طراحی سیستم های شی گرا می باشد که بسیاری از ما این اصل را رعایت نمی کنیم. OCP بیانگر این مفهوم می باشد که کدهای شما باید در برابر تغییر (modification) بسته و در برابر توسعه (extension) باز باشد.
اگر این مفهوم را نمی دانستیم و رعایت نمی کردیم، از امروز سعی کنیم هنگامی که کد نویسی می کنیم، کد خود را بررسی کنیم که آیا OCP را رعایت می کند و یا خیر، رعایت کردن OCP و قوانین کار چندان پیچیده ای نیست.
یکی از الگوهای طراحی (Design Pattern) که این اصل رعایت کرده، الگوی Factory می باشد. همانطور که در آموزش های قبلی هم گفته شد این الگو جز الگوهای ایجاد اشیا می باشد، از طریق لینک زیر می توانید این مباحث را مطالعه کنید.
آموزش Design Pattern
همانطور که می دانید کلمه Factory به معنای کارخانه است، در این روش کدهای مانند یک کارخانه عمل می کنند، کارخانه ای که وابسته به نیاز مشتری محصولی را در اختیار او می گذارد و ساخت شی محصول را به کلاسهای پایین تر می سپارد و از آوردن کلمه new در کلاس مشتری خودداری می کند.
مثل همیشه سعی می کنیم مباحث را در قالب یک مثال بیان کنیم.
در این مثال ما قصد داریم یک factory ساده ایجاد کنیم تا با گرفتن یک عدد یکی از شی های دایره، مستطیل، مربع را برای ما ایجاد کند.
شکل بالا نشان دهنده ارتباط بین 3 کلاس با کلاس Shape است، می خواهیم با ارتباط بین این 4 کلاس یک Simple Factory ایجاد کنیم. ابتدا یک interface به شکل زیر ایجاد می کنیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
public interface Ishape
{
void Draw();
}
}
حالا کلاس Circle را به صورت زیر پیاده سازی می کنیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
class Circle:Ishape
{
public void Draw()
{
Console.WriteLine("Draw one Circle");
}
}
}
کلاس های rectangle و Square را نیز به همین شکل ایجاد می کنیم. حالا مانند کلاس زیر ShapeFactory را ایجاد می کنیم:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
class ShapeFactory
{
public static Ishape GetShape(int i)
{
switch (i)
{
case 1:
Circle cir = new Circle();
cir.Draw();
return cir;
case 2:
Rectangle rec = new Rectangle();
rec.Draw();
return rec;
case 3:
Square squ = new Square();
squ.Draw();
return squ;
default: return null;
}
}
}
}
در نهایت کد تابع Main ما به صورت زیر خواهد بود
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
class Program
{
static void Main(string[] args)
{
Ishape shape = ShapeFactory.GetShape(1);
/* or Ishape shape = ShapeFactory.GetShape(2);
or Ishape shape = ShapeFactory.GetShape(3);*/
Console.ReadLine();
}
}
}
همانطور که در کد بالا مشاهده کردید، در روش factory ما Object های خود را به صورت مستقیم ایجاد نمی کنیم، بلکه مانند یک factory درخواست خود را به کلاس سازنده ارسال می کنیم و کلاس سازنده Object مورد نظر را برای ما طراحی می کند. اگر به ابتدای این مقاله بازگردیم و مفهوم OCP را مجددا بررسی می کنیم، در می یابیم که در مفهوم OCP دو اصل اساسی می بایست رعایت شود که باعث شوند کد ما در برابر تغییر (modification) بسته و در برابر توسعه (extension) باز باشد. شاید تفکیک کردن دو کلمه ی تغییر از توسعه کار چندان آسانی نباشد اما در این مثال ساده اگر دقیق شویم در می یابیم با کدهای در بدنه تابع main از کدهای سازنده کلاس یا همان factory ما مجزا هستند.
این موارد به نظر ساده در پروژه های بزرگ که تیم های متعدد قرار است بر روی کدهای مختلف کار کنند بسیار حائز اهمیت خواهد بود، زیرا شما برای انجام یک عملیات نیازی ندارید تحلیل عملیات را برای همه ی افراد و تیم ها توضیح دهید، تنها کافی است تیم توسعه دهنده عملیات ورودی ها و خروجی های مورد نظر را تعیین کنید تا دیگر تیم ها و افراد بتوانند از آن استفاده کنند.
یکی از دلایلی که اسم این Design Pattern را factory قرار داده اند همین مطلب می باشد، زیرا در دنیای واقعی نیز هیچ کارخانه ای تمامی مواد مورد نیاز خود را تولید نمی کند بلکه تنها می داند به چه ابزارهایی نیاز دارد و آنها از کارخانه های دیگر دریافت می کند و هیچگاه در گیر مسئله تولید کارخانه دیگر نمی شود.
به سراغ مثال خود باز می گردیم، در روش بالا می بایست به ازای هر نوع Object جدید یک case در switch اضافه کنیم و در تابع Main نیز مقدار عددی مربوط به شی را به تابع GetShape ارسال کنیم. طبیعتا این روش، روش مطلوبی نخواهد بود. در مثال بعدی راه حل این مشکل را بررسی خواهیم کرد.
Factory Method
یکی از روش factory روش factory method می باشد که در بخش زیر این روش را با ذکر یک مثال بررسی خواهیم کرد.
در این الگو ما یک interface دیگر تعریف می کنیم که یک method را بر می گرداند و از این طریق Object هایی را که نیاز داریم، ایجاد می کنیم. در شکل زیر UML مربوط به این الگو را مشاهده می کنید:
همانطور که در تصویر بالا مشاهده می کنید، مانند مثال قبل سه کلاس ما از یک interface ارث بری می کنند، هنگامی که می خواهیم یک Object از این سه کلاس ایجاد کنیم، درخواست ایجاد Object مورد نظر را به ShapeFactory می دهید تا این کلاس توسط متد getShape این کار را برای ما انجام دهد.
با توجه به توضیحات ارائه شده و همچنین تصویر بالا تمامی کلاس های ما از کلاس Ishape ارث می برند، کد این کلاس را در مثال قبل نیز داشتیم، این کد به صورت زیر خواهد بود:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
public interface Ishape
{
void Draw();
}
}
در اینجا کلاس ShapeFactory ما به شکل زیر پیاده سازی می شود:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
public abstract class ShapeFactory
{
public Ishape GetShape()
{
Ishape myshape = this.CreateShape();
myshape.Draw();
return myshape;
}
public abstract Ishape CreateShape();
}
}
در کلاس بالا ما دو متد یکی متد GetShape دیگری متد CreateShape داریم
متد CreateShape توسط کلاس هایی که از این کلاس ارث می برند پیاده سازی می شود.
در متد GetShape ابتدا تابع CreateShape فراخوانی می شود و خروجی آن در myshape ذخیره می شود و سپس متد myshape.Draw صدا زده می شود و نتیجه برگردانده می شود.
و سه کلاس دیگر برای RectangleFctory و CircleFactory و SquareFactory را نیز به صورت زیر تعریف کنیم:
پیاده سازی کلاس CircleFactory
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
class CircleFactory:ShapeFactory
{
public override Ishape CreateShape()
{
Circle cir = new Circle();
cir.ToString();
return cir;
}
}
}
در کد بالا ما از کلاس ShapeFactory ارث بری کرده ایم و متد CreateShape را Override یا باز نویسی کرده ایم، در بدنه ی این متد ابتدا یک Object از کلاس Circle که در بخش پیش تعریف شد ایجاد شده است و سپس مقدار این شی به ToString تبدیل شده و در نهایت شی مربوطه return شده است.
همین کدها را باید برای سایر کلاس های Rectangle و Square نیز پیاده سازی کنیم.
پیاده سازی کلاس RectangleFctory
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
class RectangleFactory:ShapeFactory
{
public override Ishape CreateShape()
{
Rectangle rec = new Rectangle();
rec.ToString();
return rec;
}
}
}
پیاده سازی کلاس SquareFactory
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
class SquareFactory:ShapeFactory
{
public override Ishape CreateShape()
{
Square sq = new Square();
sq.ToString();
return sq;
}
}
}
حالا وقتی در کلاس Main کد زیر نوشته شود:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Factory
{
class Program
{
static void Main(string[] args)
{
ShapeFactory SF = new RectangleFactory();
//ShapeFactory SF = new CircleFactory(); or
// ShapeFactory SF = new SquareFactory();
Ishape Object=SF.GetShape();
Console.ReadLine();
}
}
}
همانطور که در کد بالا مشاهده می کنید چون کلاس RectangleFactory خود از کلاس ShapeFactory ارث می برد و هر دو طبیعتا خروجی Ishape را return می کنند ما نوع SF را از ShapeFactory در نظر گرفته ایم اما در زمان ایجاد Object مقدار RectangleFactory را در آن می ریزیم.
پس در هنگامی که SF.GetShape را فراخوانی می کنیم، متد GetShape مربوط به کلاس ShapeFactory اجرا می شود، و اگر به کد کلاس ShapeFactory مجددا نگاه کنید می بینید که در متد GetShape در اولین خط متد CreateShape فراخوانی شده است، که با توجه به بازنویسی متد CreateShape توسط کلاس RectangleFactory در نتیجه کدهای این متد در بدنه کلاس RectangleFactory ابتدا اجرا می شوند و سپس نیز کدهای GetShape کلاس ShapeFactory اجرا خواهند شد.
با این کار مسئولیت ساختن شی به کلاس های پایین تر داده می شود و مدیریت بهتری در ساخت شی به وجود می آید. همچنین هنگام ساخت شی از کلاس ها هیچ اطلاعاتی از خود کلاس و نحوه ی پیاده سازی آن در دسترس مشتری نمی باشد.
اگر بخواهیم به طور ساده این مباحث را بیان کنیم در اینجا شما یک ساختار تعریف کرده اید تا دیگر برنامه نویسان از آن استفاده کنند و این امر باعث می شود که بخش های مختلف برنامه از یکدیگر جا باشند و دخل و تصرفی در بخش های هم ایجاد نکنند اما به صورت مرتبط و با یک قاعده فعالیت کنند.
مباحث تئوری معمولا نیاز به تفکر و بررسی های زیادی دارد، امیدوارم در این مطلب توانسته باشیم به خوبی اطلاعات را دهیم. در بخش بعد در خصوص مباحث Abstract Factory صحبت خواهیم کرد.
نظرات (۱)
با عرض سلام
ممنون از لطف شماف بر روی چشم حتما
موفق باشید