Design Pattern و Bridge و پرسش های متداول – بخش چهارم، در این مقاله ادامه ی پرسش های متداول الگوی طراحی بخش ۱ و ۲ و ۳ است.در این مقاله سعی خواهیم کرد تا الگوی پل( Bridge )، الگوی ترکیب/کامپوزیت ( Composite )، الگوی نما( Façade )، زنجیره ی مسئولیت(Chain Of Responsibility)، الگوی نماینده (Proxy) و الگوی قالب (Template) را درک کنیم.
اگر بخش قبلی مطلب ما را نخوانده اید همواره میتوانید آن را در لینک های زیر بخوانید.
ممکن است الگوی پل را توضیح دهید؟
الگوی پل ( Bridge ) در جداسازی انتزاع از پیاده سازی کمک میکند.
با این وجود، اگر پیاده سازی تغییر کند تاثیری بر انتزاع نخواهد داشت و بالعکس.
تصویر “انتزاع و پیاده سازی” را در نظر بگیرید. کلید، انتزاع است و تجهزات الکترونیکی، پیاده سازیها هستند.
کلید میتواند بر روی هر تجهیزات الکترونیکی اعمال شود، بنابراین کلید یک تفکر انتزاعی است درحالیکه تجهیزات، پیاده سازیها هستند.
انتزاع و پیاده سازی در Design Pattern و Bridge
بیایید سعی کنیم همین مثال کلید و تجهیزات را کدنویسی کنیم.
اول اینکه پیاده سازی و انتزاع را در دو کلاس متفاوت از یکدیگر جدا میکنیم.
تصویر “پیاده سازی” نشان میدهد که چگونه یک رابط ‘IEquipment’ با متدهای ‘Start()’ و ‘Stop()’ ساختهایم.
دو مورد تجهیزات پیاده سازی کردهایم، یکی یخچال و دیگری لامپ است.
پیاده سازی در Design Pattern و Bridge
دومین بخش، انتزاع است. کلید در مثال ما انتزاع است.
یک متد ‘SetEquipment’ دارد که شیء را تعیین و تنظیم میکند. متد ‘On’، متد ‘Start’ مربوط به تجهیزات و ‘Off’، ‘Stop’ را فراخوانی میکند.
انتزاع در Design Pattern و Bridge
در نهایت کد سرویس گیرنده را مشاهده میکنیم. میتوانید ببینید که اشیاء پیاده سازی و اشیاء انتزاع را بصورت جداگانه ایجاد کردهایم. میتوانیم از آنها به شیوهای مجزا استفاده کنیم.
کد سرویس گیرنده با استفاده از پل در Design Pattern و Bridge
ممکن است الگوی مرکب/کامپوزیت راتوضیح دهید؟
GOF definition :- A tree structure of simple and composite objects
بسیاری از اوقات، اشیاء در ساختار درختی سازمانده ی میشوند و توسعه دهندگان باید تفاوت بین اشیاء برگ و شاخه را درک کنند.
این امر موجب پیچیدگی بیشتر کد شده و ممکن است به خطاهایی بیانجامد.
برای مثال، در ادامه ساختار درختی یک شیء ساده آمده که در آن مشتری، شیء اصلی است ، که تعداد زیادی شیء آدرس دارد و هر شیء آدرس به تعداد زیادی شیء تلفن ارجاع دارد.
فرآیند کلی در Design Pattern و Bridge
حال فرض کنیم میخواهید درخت شیء کامل را درج کنید. کد نمونه چیزی شبیه به آنچه در ادامه نشان داده شده میباشد.
کد در بین تمام مشتریان، تمام آدرسهای درون شیء مشتری و تمام تلفنهای داخل اشیاء آدرس بصورت حلقهای پیش میرود.
در حالیکه این حلقه اتفاق میافتد، متدهای بروزرسانی مربوطه همانند آنچه در قطعه کد زیر نشان داده شده، فراخوانی میشوند.
foreach (Customer objCust in objCustomers) { objCust.UpDateCustomer(); foreach (Address oAdd in objCust.Addresses) { oAdd.UpdateAddress(); } foreach (Phone ophone in oAdd.Phones) { ophone.UpDatePhone(); } }
مشکلی که با کد بالا وجود دارد این است که واژگان بروزرسانی برای هر شیء تغییر میکند.
برای مشتری ‘UpdateCustomer’، برای آدرس ‘UpdateAddress’ و برای تلفن ‘UpdatePhone’ است. به بیان دیگر، با شیء اصلی و گرههای برگ مشمول بصورتی متفاوت رفتار میشود.
این امر میتواند موجب سردرگمی شده و برنامههای شما را مستعد خطا کند.
این کد میتواند تمیزتر و مرتب باشد اگر بتوانیم با شیء اصلی و برگ بطور یکسان رفتار کنیم.
میتوانید در کد زیر مشاهده کنید که یک رابط (IBusinessObject) ایجاد کردهایم که تمامی کلاسها یعنی مشتری، آدرس و تلفن را مجبور میکند تا از یک رابط مشترک استفاده کنند.
به واسطه رابط مشترک، حال تمامی اشیاء متدی به نام “Update” دارند.
foreach (IBusinessObject ICust in objCustomers) { ICust.Update(); foreach (IBusinessObject Iaddress in ((Customer)(ICust)).ChildObjects) { Iaddress.Update(); foreach (IBusinessObject iphone in ((Address)(Iaddress)).ChildObjects) { iphone.Update(); } } }
جهت پیاده سازی الگوی کامپوزیت، ابتدا یک رابط ایجاد کنید، همانطور که در قطعه کد زیر نشان داده شده است.
public interface IBusinessObject { void Update(); bool isValid(); void Add(object o); }
این رابط را در بین تمامی اشیاء ریشه و اشیاء برگ/گره همانطور که در ادامه نشان داده شده اجبار کنید.
public class Customer : IBusinessObject { private List <Address> _Addresses; public IEnumerable <Address> ChildObjects { get { return (IEnumerable <Address>)_Addresses; } } public void Add(object objAdd) { _Addresses.Add((Address) objAdd); } public void Update() { } public bool isValid() { return true; } }
پیاده سازی را بر شیء آدرس نیز تحمیل کنید.
public class Address : IBusinessObject { private List<Phone> _Phones; public IEnumerable<Phone> ChildObjects { get { return (IEnumerable<Phone>)_Phones.ToList<object>(); } } public void Add(object objPhone) { _Phones.Add((Phone)objPhone); } public void Update() { } public bool isValid() { return true; } }
پیاده سازی را بر آخرین شیء گره یعنی تلفن تحمیل کنید.
public class Phone : IBusinessObject { public void Update() {} public bool isValid() {return true;} public void Add(object o) { // no implementaton } }
ممکن است الگوی آذینگر را توضیح دهید؟
Punch :- Decorator pattern adds dynamically stacked behavior thus helping us to change the behavior of the object on runtime.
موقعیتهایی وجود دارد که میخواهیم رفتار پشتهای پویایی را به یک کلاس در زمان اجرا اضافه میکنیم.
واژهی پشته، واژهای مهم جهت تذکر و اشاره است.
برای نمونه موقعیت زیر را در نظر بگیرید که یک هتل غذاهایی به همراه نان به فروش میرساند.
آنها چهار محصول مهم دارند و سفارش میتواند با استفاده از ترکیب زیر انجام بگیرد
- نان ساده.
- نان به همراه مرغ.
- نان به همراه نوشیدنیها.
- نان به همراه مرغ و نوشیدنیها.
به بیانی دیگر، رفتار فرآیند سفارش و هزینهی سفارش در زمان اجرا بسته به نوع ترکیب تغییر میکند.
در ادامه سفارش ی ساده تنها با نان آمده که دو تابع ‘prepare’ و ‘calculatecost’ را دارد.
تمایل داریم تا بطور پویا محصولاتی جدید به این سفارش نان پایه در زمان اجرا بسته به چیزی که مشتری میخواهد اضافه کنیم.
در ادامه رابطی ساده آمده که هر سفارشی آن را خواهد داشت، یعنی Prepare و CalculateCost.
interface IOrder { string Prepare(); double CalculateCost(); }
محصول پایه نان است که رابط IOrder را پیاده سازی میکند.
میخواهیم محصولاتی جدید به سفارش نان افزوده و رفتار کل سفارش را تغییر دهیم.
public class OrderBread : IOrder { public string Prepare() { string strPrepare=""; strPrepare = "Bake the bread in oven\n"; strPrepare = strPrepare + "Serve the bread"; return strPrepare; } public double CalculateCost() { return 200.30; } }
میتوانیم سفارش نان را بصورت پویا با استفاده از الگوی آذینگر تغییر دهیم.
پیاده سازی الگوی آذینگر ، فرآیندی ۵ مرحلهای است:
مرحله ۱: یک کلاس آذینگر ایجاد کنید که شیء / رابطی که نیاز است رفتار را بصورت پویا به آن بیافزاییم را از یکدیگر جدا کند.
abstract class OrderDecorator : IOrder { protected IOrder Order; . . . . . . . . . . . . . . . }
این کلاس آذینگر شیء را جا خواهد داد و هر فراخوانی متد به شیء اصلی ابتدا تمامی اشیاء جا داده شده و سپس شیء اصلی را فراخوانی خواهد کرد.
بنابراین برای نمونه اگر متد آماده سازی (prepare) را فراخوانی میکنید، این کلاس آذینگر تمامی متدهای آماده سازی شیء جا داده شده و سپس متد آماده سازی نهایی را فراخوانی خواهد کرد.
میتوانید نحوهی تغییر خروجی هنگام ورود آذینگر به صحنه را مشاهده میکنید.
مرحله ۲: شیء جا داده شده/ اشارهگر رابط ،نیاز به مقداردهی اولیه دارد.
میتوانیم این کار را با استفاده از ابزاری متنوع انجام دهیم.
برای نمونهی زیر، تنها یک سازندهی ساده را نمایش داده و شیء را برای مقداردهی اولیه به شیء جا داده شده به سازنده میفرستیم.
abstract class OrderDecorator : IOrder { protected IOrder Order; public OrderDecorator(IOrder oOrder) { Order = oOrder; } . . . . . }
مرحله ۳: رابط IOrder را پیاده سازی و متدهای شیء جا داده شده را با استفاده از متدهای مجازی فراخوانی خواهیم کرد.
میتوانید مشاهده کنید که متدهایی مجازی ایجاد کردهایم که متدهای شیء جا داده شده را فراخوانی میکنند.
abstract class OrderDecorator : IOrder { protected IOrder Order; public OrderDecorator(IOrder oOrder) { Order = oOrder; } public virtual string Prepare() { return Order.Prepare(); } public virtual double CalculateCost() { return Order.CalculateCost(); } }
مرحله ۴: کارمان با مرحلهی مهم یعنی ایجاد آذینگر تمام شده است.
حال نیاز است تا شیء رفتار پویا که میتواند برای تغییر رفتار شیء در زمان اجرا به آذینگر اضافه شود را ایجاد کنیم.
در ادامه یک سفارش مرغ ساده آمده که میتواند به سفارش نان اضافه شود تا یک سفارش متفاوت همه با هم به نام سفارش مرغ + نان ایجاد کند.
سفارش مرغ از طریق ارث بری از کلاس آذینگر سفارش ایجاد میشود.
هر فراخوانی به این شیء که صورت میگیرد، ابتدا کارکرد دلخواه سفارش مرغ را فراخوانی کرده و سپس کارکرد شیء جا داده شده را فراخوانی میکند.
برای نمونه میتوانید ببینید هنگامیکه تابع آماده سازی فراخوانی میشود، ابتدا کارکرد آماده سازی مرغ را فراخوانی کرده و سپس کارکرد آماده سازی شیء جا داده شده را فراخوانی میکند.
محاسبه ی هزینه (calculate cost) نیز هزینهی مرغ را افزوده و سپس هزینه سفارش جا داده شده را برای جمع کل فراخوانی میکند.
class OrderChicken : OrderDecorator { public OrderChicken(IOrder oOrder) : base(oOrder) { } public override string Prepare() { return base.Prepare() + PrepareChicken(); } private string PrepareChicken() { string strPrepare = ""; strPrepare = "\nGrill the chicken\n"; strPrepare = strPrepare + "Stuff in the bread"; return strPrepare; } public override double CalculateCost() { return base.CalculateCost() + 300.12; } }
به روشی مشابه میتوانیم سفارش نوشیدنیها را نیز آماده کنیم.
class OrderDrinks : OrderDecorator { public OrderDrinks(IOrder oOrder) : base(oOrder) { } public OrderDrinks() { } public override string Prepare() { return base.Prepare() + PrepareDrinks(); } private string PrepareDrinks() { string strPrepare = ""; strPrepare = "\nTake the drink from freezer\n"; strPrepare = strPrepare + "Serve in glass"; return strPrepare; } public override double CalculateCost() { return base.CalculateCost() + 10.12; } }
مرحله ۵: مرحلهی آخر مشاهدهی الگوی آذینگر در عمل است. بنابراین از سمت سرویس گیرنده میتوانید چیزی مشابه این کد برای ایجاد یک سفارش نان بنویسید.
IOrder Order =new OrderBread(); Console.WriteLine(Order.Prepare()); Order.CalculateCost().ToString();
در ادامه نحوهی نمایش خروجی برای فراخوانی سرویس گیرندهی فوق آمده است.
Order 1 :- Simple Bread menu Bake the bread in oven Serve the bread ۲۰۰.۳
اگر تمایل دارید که سفارش را به همراه مرغ، نوشیدنی و نان ایجاد کنید، کد سرویس گیرندهی زیر در این زمینه به شما کمک خواهد کرد.
Order = new OrderDrinks(new OrderChicken(new OrderBread())); Order.Prepare(); Order.CalculateCost().ToString();
برای کد فوق، در ادامه خروجی که نوشیدنیها + مرغ + نان را ترکیب میکند آمده است.
Order 2 :- Drinks with chicken and bread Bake the bread in oven Serve the bread Grill the chicken Stuff in the bread Take the drink from freezer Serve in glass ۵۱۰.۵۴
به بیانی دیگر، اکنون میتوانید این رفتارها را به شیء اصلی ضمیمه کرده و رفتار شیء را در زمان اجرا تغییر دهید.
در ادامه ترکیب سفارشات مختلفی که میتوانیم تولید کنیم آمده است، بدین صورت رفتار سفارش را بصورت پویا تغییر میدهیم.
Order 1 :- Simple Bread menu Bake the bread in oven Serve the bread ۲۰۰.۳ Order 2 :- Drinks with chicken and bread Bake the bread in oven Serve the bread Grill the chicken Stuff in the bread Take the drink from freezer Serve in glass ۵۱۰.۵۴ Order 3 :- Chicken with bread Bake the bread in oven Serve the bread Grill the chicken Stuff in the bread ۵۰۰.۴۲ Order 4 :- drink with simple bread Bake the bread in oven Serve the bread Take the drink from freezer Serve in glass ۲۱۰.۴۲
ممکن است الگوی نما را توضیح دهید؟
الگوی نما ( Façade) بر روی گروه زیرسیستمها مینشیند و به آنها اجازه میدهد تا به شیوهای یکسان ارتباط برقرار کنند.
نما و زیرسیستم ها
تصویر “نمای سفارش” پیاده سازی عملی همین مورد را نشان میدهد. جهت سفارش دادن، نیاز است تا با کلاسهای محصول، پرداخت و صورتحساب ارتباطی متقابل داشته باشیم.
بنابراین سفارش تبدیل به یک نما میشود که کلاسهای محصول، پرداخت و صورتحساب را به یکدیگر پیوند میدهد.
نمای سفارش در Design Pattern و Bridge
تصویر “نما در عمل” نشان میدهد که چگونه کلاس ‘clsOrder’، کلاسهای ‘clsProduct’، ‘clsPayment’ و ‘clsInvoice’ را برای پیاده سازی قابلیت ‘PlaceOrder’ بهم پیوند میدهد/ بکار میگیرد.
نما در عمل در Design Pattern و Bridge
ممکن است زنجیره ی مسئولیت (COR) را توضیح دهید؟
زنجیره مسئولیت هنگامی مورد استفاده قرار میگیرد که دنبالهای از فرآیندها داشته باشیم که توسط مجموعه ای از منطق کنترل کننده مدیریت خواهد شد.
بگذارید ببینیم که این به چه معناست. موقعیتهایی وجود دارد که یک درخواست توسط مجموعهای از کنترل کنندهها مدیریت میشود.
بنابراین درخواست توسط اولین کنترل کننده برداشته میشود، که یا میتواند بخشی از آن را مدیریت کند یا نمیتواند، هنگامیکه کارش تمام شد آن را به کنترل کنندهی بعدی در زنجیره میفرستد.
این روند ادامه مییابد تا زمانیکه کنترل کنندهی مناسب آن را بردارد و فرآیند را کامل کند.
مفهوم زنجیره ی مسئولیت در Design Pattern و Bridge
بیایید سعی کنیم این مفهوم را با یک نمونه مثال کوچک درک کنیم.
تصویر “مثال نمونه” را در نظر بگیرید که در آن تعدادی منطق برای پردازش داریم.
بنابراین سه سری فرآیند داریم که در آن پیش خواهد رفت.
بنابراین فرآیند ۱ برخی پردازشها را انجام داده و آن را به فرآیند ۲ ارسال میکند.
فرآیند ۲ برخی پردازشها را انجام داده و آن را به فرآیند ۳ میفرستد تا عمل پردازش را تکمیل کند.
مثال نمونه در Design Pattern و Bridge
تصویر “نمودار کلاس برای COR” سه کلاس فرآیند را نشان میدهد که از کلاس انتزاعی یکسانی ارث میبرند.
یکی از نکات مهم جهت یادآوری این است که هر فرآیند به فرآیند بعدی که فراخوانی خواهد شد، اشاره میکند.
بنابراین در کلاس فرآیند یک شیء فرآیند دیگر به نام ‘objProcess’ را تجمیع کردهایم.
شیء ‘ObjProcess’ به فرآیند بعدی که باید پس از تکمیل این فرآیند فراخوانی شود، اشاره میکند.
نمودار کلاس برای COR در Design Pattern و Bridge
حال که کلاسهای خود را تعریف کردهایم، زمان این است که کلاسها را در سرویس گیرنده فراخوانی کنیم.
بنابراین تمامی اشیاء فرآیند را برای process1، process2 و process3 ایجاد میکنیم.
با استفاده از متد ‘setProcess’ لیست پیوندی (link list) اشیاء فرآیند را تعریف میکنیم.
میتوانید مشاهده کنید که process1 را به عنوان یک لیست پیوندی به process2 و process2 به process3 تنظیم و قرار دادهایم.
هنگامیکه این لیست پیوندی تشکیل شود، فرآیند را اجرا میکنیم که در عوض فرآیند را بر اساس لیست پیوندی تعریف شده اجرا میکند.
کد سرویس گیرنده ی COR در Design Pattern و Bridge
ممکن است الگوی نماینده را توضیح دهید؟
نماینده اساسا کلاسی است که در رابط عمل میکند که این رابط به کلاس واقعی که دادهها را دارد اشاره میکند.
این دادههای واقعی میتوانند یک تصویر عظیم یا دادههای یک شیء باشند که بسیار بزرگ بوده و نمیتوانند تکثیر شوند.
بنابراین میتوانید چندین نماینده ساخته و به حافظه عظیمی که از شیء استفاده کرده و اعمالی را اجرا میکند اشاره کنید.
این کار از تکثیر شیء اجتناب کرده و بدین صورت در حافظه صرفه جویی میکند. نمایندهها ارجاعاتی هستند که به شی واقعی اشاره میکنند.
تصویر “نماینده و شیء واقعی” نشان میدهد که چگونه یک رابط ایجاد کردهایم که توسط کلاس واقعی پیاده سازی شده است.
بنابراین رابط ‘IImageProxy’ نماینده را شکل داده و کلاس دارای پیاده سازی یعنی کلاس ‘clsActualImage’ شیء واقعی را شکل میدهد. میتوانید در کد سرویس گیرنده ببینید که رابط چگونه به شی واقعی اشاره میکند.
نماینده و شیء واقعی در Design Pattern و Bridge
مزیتهای استفاده از نماینده، امنیت و اجتناب از تکثیر اشیائی است که اندازههای عظیمی دارند.
در عوض ارسال کد میتوانیم نماینده را ارسال کنیم، بدین ترتیب از نیاز به نصب کد واقعی در طرف سرویس گیرنده پرهیز میکنیم.
تنها با نماینده در طرف سرویس گیرنده، امنیت بیشتری را تضمین میکنیم.
نکته ی دوم هنگامی است که اشیاء عظیمی داریم که انتقال آن اشیاء بزرگ در یک شبکه یا برخی دیگر دامنه ها میتواند مقدار زیادی از حافظه را مصرف کند.
بنابراین در عوض انتقال آن اشیاء بزرگ، تنها نماینده را انتقال میدهیم که منجر به عملکرد بهتری میشود.
ممکن است الگوی قالب را توضیح دهید؟
الگوی قالب یک الگوی رفتاری است. الگوی قالب یک قالب فرآیند اصلی تعریف میکند و این قالب فرآیند اصلی زیر فرآیندهایی دارد و دنباله ای که زیر فرآیندها میتوانند در آن فراخوانی شوند.
کمی بعد، زیر فرآیندهای فرآیند اصلی میتوانند برای تولید رفتاری متفاوت تغییر کنند.
Punch :- Template pattern is used in scenarios where we want to create extendable behaviors in generalization and specialization relationship.
برای مثال در ادامه فرآیندی ساده برای قالب بندی داده ها و بارگذاری آنها در oracle آورده شده است.
داده ها میتوانند از منابع متفاوتی مانند فایلها، SQL server و … بیایند.
صرف نظر از اینکه داده ها از کجا میآیند، فرآیند کلی بصورت بارگذاری داده ها از منبع، تجزیه ی داده ها و سپس ریختن آن در oracle است.
فرآیند کلی در Design Pattern و Bridge
حال میتوانیم فرآیند کلی را برای ایجاد یک فرآیند بارگذاری فایل CSV یا فرآیند بارگذاری SQL server از طریق باز تعریف پیاده سازی زیر فرآیند ‘Load’ (بارگذاری) و ‘Parse’ (تجزیه) تغییر دهیم.
فرآیند تفکر قالب در Design Pattern و Bridge
میتوانید در تصویر بالا مشاهده کنید که چگونه زیر فرآیند ‘Load’ و ‘Parse’ را برای تولید فایل CSV و فرآیند بارگذاری SQL Server تغییر داده ایم.
تابع ‘Dump’ و دنباله ی نحوه ی فراخوانی شدن زیر فرآیندها در فرآیندهای فرزند تغییر نمیکنند.
جهت پیاده سازی الگوی قالب نیاز است ۴ مرحله ی مهم را دنبال کنیم:
قالب یا فرآیند اصلی را از طریق ایجاد یک کلاس انتزاعی والد/پدر ایجاد کنید.
زیر فرآیندها را از طریق تعریف متدها و توابع انتزاعی ایجاد کنید.
متدی ایجاد کنید که دنباله ی نحوه ی فراخوانی شدن متدهای زیر فرآیند را تعریف کند.
این متد باید مانند یک متد معمولی تعریف شود تا متدهای فرزند نتوانند آن را باز تعریف (override) کنند.
در نهایت، کلاسهای فرزند را ایجاد کنید که میتوانند بروند و متدهای انتزاعی یا زیر فرآیند را برای تعریف پیاده سازی جدید تغییر دهند.
public abstract class GeneralParser { protected abstract void Load(); protected abstract void Parse(); protected virtual void Dump() { Console.WriteLine("Dump data in to oracle"); } public void Process() { Load(); Parse(); Dump(); } }
‘SqlServerParser’ از ‘GeneralParser’ ارث برده و ‘Load’ و ‘Parse’ را با پیاده سازی SQL server باز تعریف میکند.
public class SqlServerParser : GeneralParser { protected override void Load() { Console.WriteLine("Connect to SQL Server"); } protected override void Parse() { Console.WriteLine("Loop through the dataset"); } }
‘FileParser’ از ‘GeneralParser’ ارث برده و متدهای ‘Load’ و ‘Parse’ را با پیاده سازی های مختص فایل باز تعریف میکند.
public class FileParser : GeneralParser { protected override void Load() { Console.WriteLine("Load the data from the file"); } protected override void Parse() { Console.WriteLine("Parse the file data"); } }
حال میتوانید از سرویس گیرنده هر دو تجزیه گر (parser) را فراخوانی کنید.
FileParser ObjFileParser = new FileParser(); ObjFileParser.Process(); Console.WriteLine("-----------------------"); SqlServerParser ObjSqlParser = new SqlServerParser(); ObjSqlParser.Process(); Console.Read();
خروجی هر دو تجزیه گر در Design Pattern و Bridge
Load the data from the file Parse the file data Dump data in to oracle ----------------------- Connect to SQL Server Loop through the dataset Dump data in to oracle
هیچ دیدگاهی نوشته نشده است.