آشنایی با اصول .S.O.L.I.D (بخش پنجم - DIP):


با عرض سلام و احترام.
پیشاپیش از شما دوست عزیز و گرامی، بابت وقتی که برای مطالعه ی این مطلب خواهید گذاشت، سپاسگزارم.
تقاضا دارم، در صورت مشاهده ی اشتباه متنی یا محتوایی، به اینجانب اطلاع دهید تا (ضمن کمک به یادگیری بنده) در اسرع وقت برای اصلاح متن اقدام نمایم.
شماره ی تماس:
09215149218
نشانی پست الکترونیکی:
RezaQadimi.ir@Gmail.com
آدرس سایت ها:
https://Reza-Qadimi.ir - https://WannaDate.ir

هدف من در این مقاله، آشنایی شما با چهارمین اصل از اصول .S.O.L.I.D، یعنی DIP یا Dependency Inversion Principle است.


اصل DIP بیان میکند:

ماژول (کلاس) سطح بالا، نباید به ماژول های سطح پایین تر وابستگی داشته باشند، و وابستگی هر دو آن ها، باید به abstraction باشد. همینطور باید گفت abstraction نباید به جزئیات وابستگی داشته باشد و این جزئیات هستند که به abstraction وابستگی دارند.

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

  1. کلاس های سطح بالای ما، نباید به کلاس های سطح پایین وابستگی داشته باشند.
  2. وابستگی کلاس های سطح بالا، و کلاس های سطح پایین ما باید به abstraction باشد.
  3. این abstraction نیست که به جزئیات وابستگی دارد، بلکه جزئیات هستند که به abstraction وابسته اند.



به این مثال توجه کنید:

public class SalaryCalculator : object
{
        public SalaryCalculator() : base()
        {
        }

        public decimal CalculateSalary(int hoursWorked, decimal hourlyRate)
        {
                decimal result = hoursWorked * hourlyRate;

                return result;
        }
}

نکته: هر چند میتوانیم تابع "CalculateSalary" را به این صورت بنویسیم:

public decimal CalculateSalary
        (int hoursWorked, decimal hourlyRate) => hoursWorked * hourlyRate ;

اما از آنجایی که در زمان دیباگ، امکان trace کردن روند محاسبه و نتیجه ی نهایی را نداریم، از این امکان استفاده نمیکنیم.


  • می بینید که برای محاسبه ی حقوق کارمندان، باید از کلاس "SalaryCalculator" یک آبجکت ساخته و با فراخوانی متد "CalculateSalary" آن، حقوق کارمند مورد نظر را محاسبه کنیم (این یعنی تزریق وابستگی):
public class EmployeeDetails : object
{
        public EmployeeDetails() : base()
        {
        }

        public int HourlyRate { get; set; }
        public int HoursWorked { get; set; }

        public decimal GetSalary()
        {
                SalaryCalculator  salaryCalculator = new();

                decimal result = SalaryCalculator.CalculateSalary
                        (hoursWorked: HourseWorked, hourlyRate: HourlyRate);

                return result;
        }
}

مثال بالا، نمونه ی بارز نقض DIP ست، چرا که (با این روش پیاده سازی) کلاس سطح بالای ما (یعنی "EmployeeDetails")، به کلاس سطح پایین ما (یعنی "SalaryCalculator") وابسته شده است.

اما چگونه میتوان این مشکل را برطرف کرد؟


بیایید دوباره اصل Dependency Inversion را با هم مرور کنیم:

کلاس سطح بالای ما، نباید به کلاس های سطح پایین وابستگی داشته باشد، و وابستگی هر دوی آن ها باید به "abstraction" باشد.


  • برای رفع این مشکل، ما اینترفیس (یا abstract class ی) ایجاد کرده، و متد "CalculateSalary" را (با پارامترهای ورودی و خروجی مشخص شده) در آن تعریف می نماییم:
public interface ISalaryCalculator
{
        decimal CalculateSalary(int hoursWorked, decimal hourlyRate);
}


  • کلاس "SalaryCalculator" به عنوان کسی که از اینترفیس "ISalaryCalculator" ارث بری کرده، موظف به پیاده سازی موجودات تعریف شده در آن است:
public class SalaryCalculator : object, ISalaryCalculator 
{
        public SalaryCalculator() : base()
        {
        }

        public decimal CalculateSalary(int hoursWorked, decimal hourlyRate)
        {
                decimal result = hoursWorked * hourlyRate;

                return result;
        }
}


public class EmployeeDetails : object
{
         public EmployeeDetails(ISalaryCalculator salaryCalculator) : base()
         {
                 SalaryCalculator = salaryCalculator;
         }

         public int HourlyRate { get; set; }
         public int HoursWorked { get; set; }
         public ISalaryCalculator SalaryCalculator { get; }

          public decimal GetSalary()
         {
                 decimal result = SalaryCalculator.CalculateSalary
                         (hoursWorked: HourseWorked, hourlyRate: HourlyRate);

                  return result;
         }
 }

همانطور که میبینید، وابستگی ما دیگر به کلاس "SalaryCalculator" نیست، و به اینترفیس "ISalaryCalculator" معکوس (یا اصطلاحا invert) شده است. این بدان معناست که ما دیگر با این که چه کسی متد "CalculateSalary" را پیاده سازی کرده است، کاری نداریم و صرفا با لایه ی abstract آن در ارتباط هستیم.


معرفی:
رضا قدیمی هستم. برنامه نویس و دانش آموزِ حوزه ی وب، بسیار مشتاق در یادگیری مفاهیم و اطلاعات جدید در این حوزه.