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


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

هدف من در این مقاله، آشنایی شما با دومین اصل از اصول .S.O.L.I.D، یعنی OCP یا Open/Closed Principle است.


اصل OCP بیان کننده ی این موضوع است که:

موجودیت های پروژه ی ما (کلاس، متد، ...)، باید امکان توسعه داشته باشند، اما اجازه ی تغییر نه!

یا به عبارتی میتوان گفت:

زمانی که پیاده سازی یک کلاس یا متد به طور کامل انجام شد، آن کلاس/متد باید برای تغییرات بسته باشد (نباید اجازه ی تغییر منطق و یا functionality آن کلاس یا متد را بدهیم).

البته باید گفت، این بدان معنا نیست که اجازه ی refactoring، یا برطرف کردن خطاها، یا حتی بهبود performance را نداریم!

مثال مناسب OCP در دنیای واقعی "مخلوط کن" است!

همانطور که میدانید، ما میتوانیم بر حسب نیاز خود با استفاده از سری های مختلف از مخلوط کن استفاده کنیم (به عبارتی آن را توسعه بدهیم)، اما نمیتوانیم functionality آن را تغییر دهیم!


به کد زیر توجه کنید:

public class SavingAccount : object, ISavingAccount 
{
        public SavingAccount() : base()
        {
        }

        //other method and property and code...

        public decimal CalculateInterest(IBankAccount account )
        {
                //Calculate interest for different account based on rules and regulation of bank
                if (account.AccountType == Enums.AccountType.Regular)
                {
                        Interest = account.Balance * 0.4;

                        if (account.Balance < 1000)
                        {
                                Interest -= account.Balance * 0.2;
                        }
                        else if (account.Balance < 5000)
                        {
                                Interest += account.Balance * 0.4;
                        }
                }
                else if (account.AccountType == Enums.AccountType.Salary)
                {
                                Interest = account.Balance * 0.5;
                }

                return Interest ;
        }
}

هر بانک، بر حسب نوع حساب مشتری، سود متفاوتی را (به صورت ماهیانه، یا...) برای او در نظر می گیرد. همچنین بر اساس نوع حساب، قوانین متفاوتی برای محاسبه ی میزان سود وجود دارد.

برای محاسبه نرخ سود هر حساب، برنامه نویسان یک کلاس به نام "SavingAccount"، با متدی جهت محاسبه ی نرخ سود، پیاده سازی کرده اند.

بر این اساس، متد "CalculateInterest" در کلاس "SavingAccount"، وظیفه ی محاسبه ی نرخ سود، بر حسب نوع حساب را بر عهده دارد.

این نوع پیاده سازی، اصل OCP یا Open/Closed Principle را نقض کرده است! چرا؟

چون در صورت معرفی نوع دیگری از حساب توسط بانک، برنامه نویس مجبور به تغییر business مربوط به متد "CalculateInterest"خواهد بود، و باید قوانین مربوط به محاسبه ی نرخ سود حساب جدید را در این متد پیاده سازی کند.

علاوه بر این موضوع، همانطور که مشاهده می نمایید، تابع "CalculateInterest" یکی دیگر از اصول SOLID، یعنی SRP (برای مطالعه، بر روی این لینک کلیک کنید) را نیز نقض کرده است، چرا که این متد وظیفه ی محاسبه ی نرخ سود، برای بیش از یک نوع حساب را بر عهده دارد!


چگونه می توانیم Open/Closed Principle را پیاده سازی نماییم؟

برای پیاده سازی اصل OCP می توانیم از ارث بری یا Inheritance کمک بگیریم:

public class RegularSavingAccount : object, ISavingAccount
{
         public RegularSavingAccount() : base()
         {
         }

        //other method and property and code related to Regular Saving account

        // Calculate interest for regular saving account based on rules and regulation of bank
        public decimal CalculateInterest(IBankAccount account)
        {
                Interest = account.Balance * 0.4;

                if (account.Balance < 1000)
                {
                        Interest -= account.Balance * 0.2;
                }
                else if (account.Balance < 5000)
                { 
                        Interest += account.Balance * 0.4;
                }

                return Interest;
        }
}


public class SalarySavingAccount : object, , ISavingAccount
{
         public SalarySavingAccount() : base()
         {
         }

        // Calculate interest for saving account based on rules and regulation of bank
        public decimal CalculateInterest()
        {
                Interest = account.Balance * 0.5;

                return Interest;
        }
}

در روش دوم، به ازای هر نوع حساب، یک کلاس ایجاد شده که از اینترفیس "ISavingAccount" ارث بری کرده اند (می توان از کلاس abstract برای این هدف استفاده کرد).

چنانچه نوع حساب جدیدی توسط بانک معرفی شود، نیازی به تغییر منطق کلاس های موجود نداریم، و صرفا از طریق ارث بری کلاس جدید، از اینترفیس "ISavingAccount"، می توانیم functionality مربوطه را توسعه دهیم.

علاوه بر این، می بینید که به کمک این روش اصل Single Responsibility نیز رعایت شده، چرا که هر کلاس یا متد ما، صرفا و فقط یک وظیفه را بر عهده دارد.


معایب عدم رعایت OCP:

  1. از آنجا که امکان و اجازه ی تغییر منطق کلاس و/یا متدهای آن را داده ایم، با تغییر business مربوط به هر کدام از این موجودیت ها، موظف به تست functionality آن به طور کامل خواهیم بود.
  2. عدم رعایت Open/Closed Principle، منجربه نقض Single Responsibility Principle خواهد شد، چرا که باعث می شود کلاس و یا متد ما چند وظیفه ی مختلف را بر عهده بگیرد.
  3. توسعه ی کلاس و یا متد ما سخت خواهد شد و از آنجا که میزان کد نوشته شده در هر کلاس/متد بیشتر و بیشتر می شود، امکان فهم آن هم به همان میزان کاهش می یابد.

نکته: اصول SRP و OCP، به شکل ویژه ای به هم وابستگی دارند، و در صورت عدم رعایت هر کدام از آن ها، اصل دیگر نیز نقض خواهد شد.


پی نوشت: در مقاله ی بعد، به بررسی اصل Liskov Substitution Principle خواهیم پرداخت.


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