آشنایی با اصول .S.O.L.I.D (بخش چهارم - ISP):


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

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


اصل Interface Segregation، یکی دیگر از اصول SOLID است، که توسط Robert C. Martin معرفی شد.

این اصل بیان کننده ی آن است که:

کلاینت (کلاسی که وظیفه ی پیاده سازی Interface را بر عهده دارد)، نباید مجبور به پیاده سازی موجوداتی شود که به آن ها احتیاجی ندارد و از آن ها استفاده ای نمی کند.

به عبارتی این اصل به ما توصیه می کند که:

از طراحی اینترفیس های چاقی که کلاینت (کلاسی که وظیفه ی پیاده سازی Interface را بر عهده دارد) را مجبور به پیاده سازی ویژگی ها و رفتارهایی میکند که به آن نیازی ندارد، جلوگیری کنیم، و به جای آن اینترفیس های لاغرتری (کوچک تر) طراحی کنیم، که صرفا دارای ویژگی ها و رفتارهایی هستند که کلاس پیاده سازی کننده ی آن اینترفیس، به آنها نیاز دارد.

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

public interface IPayment
{
        T Status<T>();
        void InitiatePayment();
        System.Collections.Generic.IList<T> GetPayments<T>();
}


  • کلاس "BankPayment"، از اینترفیس IPayment ارث بری، و آن را پیاده سازی میکند:
public class BankPayment : object, IPayment
{
        public BankPayment() : base()
        {
        }

        public T Status<T>()
         {
                 // ...
         }

        public void InitiatePayment()
        {
                // ...
        }

        public System.Collections.Generic.IList<T> GetPayments<T>()
        {
                // ...
        }
}

نکته: برای کاهش پیچیدگی، پیاده سازی business را کنار گذاشته و به سراغ مفاهیم میرویم.


بعد از مدتی، از طرف بانک اعلام میکنند:

"علاوه بر سرویس پیاده سازی شده، به سرویس "LoanPayment" هم نیاز داریم."

و در ادامه بیان میکنند که:

"سرویس "Loan Payment" تا حدودی شبیه سرویس "Bank Payment" است، اما با عملیات های بیشتر."

  • برای شروع، در اینترفیس "IPayment"، رفتارهای مربوط به این سرویس را نیز اضافه میکنیم:
public interface IPayment
{
         // Original Method(s)

         void InitiateRePayment();
         void InitiateLoanSettlement();
}


  • و سرویس "BankPayment" که از "IPayment" ارث بری کرده است را پیاده سازی می نماییم:
public class LoanPayment : object, IPayment
{
        public BankPayment() : base()
        {
        }

        public void InitiatePayment()
        {
                // throw some exception...
        }

        public T Status<T>()
        {
                // ...
        }

        public System.Collections.Generic.IList<T> GetPayments<T>()
        {
                // ...
        }

        public void InitiateRePayment()
        {
                // ...
        }

         public void InitiateLoanSettlement()
        {
                // ...
        }


  • از آنجا که اینترفیس "IPayment" دست خوش تغییرات شده (اضافه شدن متدهای InitiateRePayment و InitiateLoanSettlement)، کلاینت (کلاس ارث بری کرده از این اینترفیس) مجبور به پیاده سازی این توابع در کلاس "BankPayment" میشود:
public class BankPayment : object, IPayment
{
        public BankPayment() : base()
        {
        }

        public void InitiatePayment()
        {
                // ...
        }

        public T Status<T>()
        {
                // ...
        }

        public System.Collections.Generic.IList<T> GetPayments<T>()
        {
                // ...
        }

        public void InitiateRePayment()
        {
                // throw some exception...
        }

         public void InitiateLoanSettlement()
        {
                // throw some exception...
        }
}

مسئله اینجاست، که پیاده سازی این رفتارها از سر ناچاری و کاملا ناخواسته است، و در آینده منجربه مشکلات جانبی زیادی میشود. اما راه حل چیست؟


  • برای برطرف کردن این مشکل، اینترفیس چاق خود را به تعدادی اینترفیس کوچک تر میشکنیم.
  • حالا ما یک اینترفیس مشترک داریم (Base یا Common)، که دارای توابعی است که در هر دو سرویس مورد نیاز است:
public interface IPayment
{
        T Status<T>();
        System.Collections.Generic.IList<T> GetPayments<T>();
}


  • از آنجا که (در این مثال) دو سرویس مختلف برای Payment داریم، برای هر کدام از آن ها یک اینترفیس میسازیم، که از اینترفیس مشترک ما (IPayment) ارث بری کرده، و توابع و موجودات مورد نیاز مخصوص آن سرویس را به توابع موجود در اینترفیس پایه اضافه/extend میکند:
public interface IBankPayment : IPayment
{
        void InitiatePayment();
}


public interface ILoanPayment : IPayment
{
        void InitiateRePayment();
        void InitiateLoanSettlement();
}


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

        public void InitiatePayment()
        {
                // ...
        }

        public T Status<T>()
        {
                // ...
        }

        public System.Collections.Generic.IList<T> GetPayments<T>()
        {
                // ...
        }
}


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

        public void InitiateRePayment()
        {
                // ...
        }

        public void InitiateLoanSettlement()
        {
                // ...
        }

        public T Status<T>()
        {
                // ...
        }

        public System.Collections.Generic.IList<T> GetPayments<T>()
        {
                // ...
        }
}


میبینید که با جا کردن اینترفیس ها، و تبدیل یک اینترفیس چاق به اینترفیس های کوچکتر، دیگر نیازی به پیاده سازی موجوداتی که مورد نیاز نیستند، نخواهیم بود و صرفا رفتارها و ویژگی هایی را پیاده سازی میکنیم، که در سرویس خود به آن نیاز داریم.


معیاب عدم رعایت اصل Interface Segregation:

  1. کلاینت مجبور به پیاده سازی موجوداتی میشود که به آن ها نیازی ندارد.
  2. پیاده سازی موجودیت هایی که کلاینت به آنها نیازی ندارد، موجب کاهش خوانایی کد، و همچنین افزایش پیچیدگی آن برای برنامه نویس می شود.

طراحی اینترفیس ها به این شکل، گاها موجب نقض اصل Single Responsibility می شود، چرا که باعث میشود بعضی از مواقع (سهوا) موجودات غیر مرتبط را در یک اینترفیس قرار بدهیم.


پی نوشت:

اصل Liskov Substitution (می توانید اینجا مطالعه کنید) در رابطه با Subtype ها و مفهوم Inheritance است، اما Interface Segregation Pinciple در رابطه با Business Logic ارتباط با کلاینت هاست.


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


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