در این مقاله ما قصد داریم به یک الگوی طراحی دیگر در سی شارپ بپردازیم که این بار یک الگوی ساختاری است. این الگو، الگوی Facade است.
این مقاله، بخشی از مجموعه آموزشی زیر است:
- الگوی طراحی Builder و Fluent Builder
- اینترفیس Fluent Builder به همراه Generic بازگشتی
- Facated Builder
- متد Factory
- Singleton
- Adapter
- Composite
- Decorator
- Command
- Strategy
- Facade
سورس کد در این لینک موجود است: الگوی طراحی Facade – سورس کد.
برای مشاهده لیست کامل مقالات این مجموعه آموزشی، الگوهای طراحی #C را بررسی کنید.
برای شروع، ما قصد داریم این مقاله را به بخش های زیر تقسیم کنیم:
الگوی Facade چیست؟
همانطور که از نام آن پیداست، نمایانگر یک “نما” برای کاربر نهایی است تا استفاده از زیرسیستم هایی را که طراحی ضعیف و یا خیلی پیچیده دارند را با پنهان کردن جزئیات پیاده سازی آنها، ساده سازی کند. همچنین هنگام کار با کتابخانه های پیچیده و API ها مفید است.
یک الگوی Facade با یک رابط کلاس واحد نشان داد میشود که بدون زحمت تغییر خود زیرسیستم ها، خواندن و استفاده از آن ساده و آسان است. با این حال، ما باید مراقب باشیم، زیرا محدود کردن استفاده از عملکردهای زیرسیستم ممکن است برای کاربران قدرتمند جالب نباشد.
مثال الگوی Facade
به عنوان یک مثال، برای بیان بهتر الگوی Facade، قصد داریم workflow سفارش آنلاین غذا را شرح دهیم.
فرض کنید فهرستی از رستوران ها را داریم. صفحه رستوران را باز میکنیم. غذای مورد علاقه خود را پیدا می کنیم و به سبد خرید اضافه می کنیم. هر چند بار که بخواهیم این کار را انجام می دهیم و سفارش را تکمیل می کنیم. هنگام ارسال سفارش، تاییدیه سفارش به همراه قیمت سفارش را دریافت می کنیم.
ابتدا، بیایید یک کلاس به نام Order ایجاد کنیم که نشان دهنده سفارشی است که از سمت کاربر می آید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Order { public string DishName { get; set; } public double DishPrice { get; set; } public string User { get; set; } public string ShippingAddress { get; set; } public double ShippingPrice { get; set; } public override string ToString() { return string.Format("User {0} ordered {1}. The full price is {2} dollars.", User, DishName, DishPrice + ShippingPrice); } } |
علاوه بر این، باید دو کلاس دیگر اضافه کنیم. یکی online restaurant و دیگری shipping service. کلاس OnlineRestaurant
، متدهایی را برای افزودن سفارشات به سبد خرید ارائه میدهد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class OnlineRestaurant { private readonly List<Order> _cart; public OnlineRestaurant() { _cart = new List<Order>(); } public void AddOrderToCart(Order order) { _cart.Add(order); } public void CompleteOrders() { Console.WriteLine("Orders completed. Dispatch in progress..."); } } |
از طرف دیگر، کلاس ShippingService سفارش را گرفته و آنها را به آدرس ذخیره شده در ویژگی ShippingAddress داخل کلاس Order ارسال می کند. ShippingService همچنین هزینه های حمل و نقل را محاسبه کرده و به کاربر نمایش می دهد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class ShippingService { private Order _order; public void AcceptOrder(Order order) { _order = order; } public void CalculateShippingExpenses() { _order.ShippingPrice = 15.5; } public void ShipOrder() { Console.WriteLine(_order.ToString()); Console.WriteLine("Order is being shipped to {0}...", _order.ShippingAddress); } } |
در پایان، ما کل منطق را در کلاس Main
گنجانده ایم تا workflow سفارش آنلاین غذا را نشان دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Program { static void Main(string[] args) { var restaurant = new OnlineRestaurant(); var shippingService = new ShippingService(); var chickenOrder = new Order() { DishName = "Chicken with rice", DishPrice = 20.0, User = "User1", ShippingAddress = "Random street 123" }; var sushiOrder = new Order() { DishName = "Sushi", DishPrice = 52.0, User = "User2", ShippingAddress = "More random street 321" }; restaurant.AddOrderToCart(chickenOrder); restaurant.AddOrderToCart(sushiOrder); restaurant.CompleteOrders(); shippingService.AcceptOrder(chickenOrder); shippingService.CalculateShippingExpenses(); shippingService.ShipOrder(); shippingService.AcceptOrder(sushiOrder); shippingService.CalculateShippingExpenses(); shippingService.ShipOrder(); Console.ReadLine(); } } |
نتیجه پیاده سازی فعلی کلاس Main به صورت زیر است:
نکته: فرض ما بر این است که سفارشات ما از بیرون می آید و به این دلیل است که آنها را داخل کلاس Main
ایجاد کرده ایم. این اختیاری است، البته، برای سیستم های پیچیده تر، این نیز بخشی از service است.
حال، برای کسانی از شما که در تعجب هستید:
“چرا این اشتباه است؟”
در ادامه به آن میپردازیم.
با نگاهی به کلاس Main
و تمام مراحلی که پیاده سازی کرده ایم، می توانیم کدهای زیادی را در یک کلاس واحد مشاهده کنیم. از آنجایی که تمایل داریم مطالب را جهت خواندن آسانتر کرده و از پیچیدگی آن بکاهیم، باید تنظیماتی را انجام دهیم.
و این جایی است که الگوی Facade وارد عمل میشود.
پیاده سازی الگوی Facade
یکی از اهداف الگوی Facade مخفی کردن جزئیات پیاده سازی است که نشان می دهد وجود همه چیز در کلاس Main
درست نیست. این اطلاعات بیش از حد غیر ضروری است، بنابراین، ما دوست داریم آن را در جای بهتری داشته باشیم.
با این اوصاف، ما قصد داریم کلاس دیگری به نام Facade ایجاد کنیم. کلاس Facade به عنوان یک “میان افزار” بین کاربر و پیچیدگی سیستم بدون تغییر business logic عمل میکند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class Facade { private readonly OnlineRestaurant _restaurant; private readonly ShippingService _shippingService; public Facade(OnlineRestaurant restaurant, ShippingService shippingService) { _restaurant = restaurant; _shippingService = shippingService; } public void OrderFood(List<Order> orders) { foreach (var order in orders) { _restaurant.AddOrderToCart(order); } _restaurant.CompleteOrders(); foreach (var order in orders) { _shippingService.AcceptOrder(order); _shippingService.CalculateShippingExpenses(); _shippingService.ShipOrder(); } } } |
از آنجایی که ما منطق پیاده سازی را به کلاس Facade منتقل کرده ایم، می توانیم کلاس Main را ساده سازی کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Program { static void Main(string[] args) { var restaurant = new OnlineRestaurant(); var shippingService = new ShippingService(); var facade = new Facade(restaurant, shippingService); var chickenOrder = new Order() { DishName = "Chicken with rice", DishPrice = 20.0, User = "User1", ShippingAddress = "Random street 123" }; var sushiOrder = new Order() { DishName = "Sushi", DishPrice = 52.0, User = "User2", ShippingAddress = "More random street 321" }; facade.OrderFood(new List<Order>() { chickenOrder, sushiOrder }); Console.ReadLine(); } } |
وقتی کد را اجرا می کنیم، می توانیم همان خروجی دقیق را ببینیم:
این بدان معنی است که ما با موفقیت، کاربر را از فشار غیر ضروری دانستن تمام مراحل لازم برای رسیدن غذا رها کرده ایم.
توجه: در این مثال، OnlineRestaurant و ShippingService را به Facade منتقل کرده ایم، با این فرض که از قبل ایجاد شده اند. با این حال، آنها را می توان در داخل خود Facade نیز نمونه سازی کرد.
نتیجه گیری
بنابراین، ما دیدیم که چگونه الگوی Facade می تواند به ما در آسان کردن کار client کمک کند. اکنون، ما آماده ایم پیاده سازی های پیچیده را همانطور که هست بپذیریم. و نکته آخر اینکه در شرایط خاص، استفاده از این الگو مستلزم دقت است. چرا که آن میتواند تواناییهای کاربر را برای استفاده از پتانسیل کامل برنامه یا کتابخانهای که ما در تلاش برای سادهسازی آن هستیم، محدود کند.