در دو قسمت قبلی هر Principle از استانداردهای SOLID را نام بردیم و حال وقت شرح و بررسی هر یک از آنهاست . اولین و شاید بتوان گفت مهمترین آیتم همان SRP است که شرح و بررسی آن خواهیم پرداخت .
بسمه تعالی
آموزش SOLID Programming Principles - بخش سوم
در دو قسمت قبلی هر Principle از استانداردهای SOLID را نام بردیم و حال وقت شرح و بررسی هر یک از آنهاست . اولین و شاید بتوان گفت مهمترین آیتم همان SRP است که شرح و بررسی آن خواهیم پرداخت .
§ Single responsibility principle (SRP)
در عمل کاراکتر S در عبارت SOLID به دلیل همین SRP می باشد.
در عمل SRP به این معناست که هر Object باید فقط یک دلیل برای دستکاری و فقط یک وظیفه انجام داشته باشد.به عبارت دیگر، هر Object باید فقط انجام دهنده یک وظیفه و تحرک باشد. همه ما در گذشته هم چنین مفهومی را با استفاده از Method ها، Domain Model یا Business Layer پیاده سازی کرده ایم . به عنوان مثال، اگر یک عملیات Insert در دیتابیس قرار است اجرا شود، کلاس مورد نظر ما دیگر وظیفه Exception Handling نبایست داشته باشد و برای آن می بایست یک Object دیگر ایجاد و صدا زده شود.
به صورت کلی، هدف ما از پیاده سازی و رعایت قواعد SRP به طور اجمالی شرح زیر است .
· ایجاد اشیا به صورت مختصر و فاقد پیچیدگی و جلوگیری از ایجاد کلاسهایی با به صورت درهم که وظیفه انجام چند عمل را بر عهده داشته باشند. در کل پیشنهاد میشود از ایجاد چاقوهای سوییسی جلوگیری شود.
· افزایش امکان تست پذیری از طریق ایجاد قواعد تک منظوره در روالهای کدنویسی.
· افزایش خوانایی کد و ارتقای سطح کار تیمی.
· افزایش و ساده سازی روند تعمیر پذیری کدها
شاید با عبارت Separation of Concerns یا به صورت اختصاری SOC آشنا باشید. در واقع SRP همان پیاده سازی صحیح مفاهیم SOC خواهد بود. هر Object یک وظیفه جهت انجام داشته باشد و از مخلوط شدن وظایف و بخشها جلوگیری گردد.
در ادامه بهتر است با دو مفهوم زیر آشنا شویم.
در عمل Cohesion به معنای انسجام ماژولها و اختصاص وظایف به آنهاست و Coupling به معنای ارتباط درگیری هر ماژول با دیگری و اختلاط آنها با همدیگر میباشد.
در یک طراحی نرم افزار استاندارد، باید میزان Cohesion را بالا برده و Coupling را به حداقل برسانیم.اگر قرار باشد یک کلاس به دلایلی دستخوش تغییر گردد، باید دقیقا و دقیقا همان کلاس تغییر نماید و نیاز به دست زدن به دیگر Object ها باید در حد صفر یا در حداقل میزان ممکنه باشد. اگر قصد دارید میزان درستی پیاده سازی SRP را در کدهای خود بررسی کنید، به سراغ TDD رفته و اختصاصن از Test First Approach استفاده نمایید. در این حالت میتوان میزان و سطح پیاده سازی SRP در کدهای شما تا حدود زیادی مشخص خواهد شد.
خوب حال وقت آن است که یک برای مثال یک پروژه e-commerce را به عنوان مثال در یک وضعیت در هم بر هم و بدون پیاده سازی SRP مورد بررسی قرار دهیم.
در VS یک پروژه Console ایجاد کنید و درون آن یک فولدر به نام Model ایجاد نمایید.
- public class OrderItem
- {
- public string Identifier { get; set; }
- public int Quantity { get; set; }
- }
- public class ShoppingCart
- {
- public decimal TotalAmount { get; set; }
- public IEnumerable<OrderItem> Items { get; set; }
- public string CustomerEmail { get; set; }
- }
- public enum PaymentMethod
- {
- CreditCard
- , Cheque
- }
- public class PaymentDetails
- {
- public PaymentMethod PaymentMethod { get; set; }
- public string CreditCardNumber { get; set; }
- public DateTime ExpiryDate { get; set; }
- public string CardholderName { get; set; }
- }
پس از Model به سراغ متدهایی خواهیم رفت که وظیفه دریافت سفارشات و ... را بر عهده دارند.
- public class Order
- {
- public void Checkout(ShoppingCart shoppingCart, PaymentDetails paymentDetails, bool notifyCustomer)
- {
- if (paymentDetails.PaymentMethod == PaymentMethod.CreditCard)
- {
- ChargeCard(paymentDetails, shoppingCart);
- }
-
- ReserveInventory(shoppingCart);
-
- if (notifyCustomer)
- {
- NotifyCustomer(shoppingCart);
- }
- }
-
- public void NotifyCustomer(ShoppingCart cart)
- {
- string customerEmail = cart.CustomerEmail;
- if (!String.IsNullOrEmpty(customerEmail))
- {
- try
- {
-
- }
- catch (Exception ex)
- {
-
- }
- }
- }
-
- public void ReserveInventory(ShoppingCart cart)
- {
- foreach (OrderItem item in cart.Items)
- {
- try
- {
- InventoryService inventoryService = new InventoryService();
- inventoryService.Reserve(item.Identifier, item.Quantity);
-
- }
- catch (InsufficientInventoryException ex)
- {
- throw new OrderException("Insufficient inventory for item " + item.Sku, ex);
- }
- catch (Exception ex)
- {
- throw new OrderException("Problem reserving inventory", ex);
- }
- }
- }
-
- public void ChargeCard(PaymentDetails paymentDetails, ShoppingCart cart)
- {
- PaymentService paymentService = new PaymentService();
-
- try
- {
- paymentService.Credentials = "Credentials";
- paymentService.CardNumber = paymentDetails.CreditCardNumber;
- paymentService.ExpiryDate = paymentDetails.ExpiryDate;
- paymentService.NameOnCard = paymentDetails.CardholderName;
- paymentService.AmountToCharge = cart.TotalAmount;
-
- paymentService.Charge();
- }
- catch (AccountBalanceMismatchException ex)
- {
- throw new OrderException("The card gateway rejected the card based on the address provided.", ex);
- }
- catch (Exception ex)
- {
- throw new OrderException("There was a problem with your card.", ex);
- }
-
- }
- }
-
- public class OrderException : Exception
- {
- public OrderException(string message, Exception innerException)
- : base(message, innerException)
- {
- }
- }
پس از کلاس Order یک فولدر به نام Services ساخته و بخشهای Inventory و Payment را اضافه مینماییم.
- public class InventoryService
- {
- public void Reserve(string identifier, int quantity)
- {
- throw new InsufficientInventoryException();
- }
- }
-
- public class InsufficientInventoryException : Exception
- {
- }
- public class PaymentService
- {
- public string CardNumber { get; set; }
- public string Credentials { get; set; }
- public DateTime ExpiryDate { get; set; }
- public string NameOnCard { get; set; }
- public decimal AmountToCharge { get; set; }
- public void Charge()
- {
- throw new AccountBalanceMismatchException();
- }
- }
-
- public class AccountBalanceMismatchException : Exception
- {
- }
دقت کنید که این متدها کار خاصی به جز باز گرداندن Exception انجام نخواهند داد.حال وقت آن است ببینیم ایراد کدهای ایجاد شده چیست و چگونه میتوان بر اساس قواعد SRP آنرا تصحیح نمود. در بخش بعدی به بررسی این موضوع خواهیم پرداخت.