آموزش اصل Interface Segregation از اصول SOLID

برنامه‌نویسی حرفه‌ای چیزی فراتر از نوشتن کدهایی است که کار می‌کند! این مهارت شامل طراحی اصولی، انعطاف‌پذیر و قابل نگهداری نیز می‌شود. اصل Interface Segregation به ما کمک می‌کند تا وابستگی‌های غیرضروری در کد را کاهش داده و توسعه‌ی آن را آسان‌تر کنیم. در این آموزش به زبان ساده و با مثال‌های واقعی این اصل را توضیح داده و بررسی می‌کنیم.

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

اصل Interface Segregation چیست؟

اصل جداسازی اینترفیس‌ها یا اصل Interface Segregation می‌گوید که «اینترفیس‌های بزرگ و همه‌کاره نباید کلاس‌ها را مجبور به پیاده‌سازی متدهایی کنند که نیازی به آن‌ها ندارند. اینترفیس‌ها باید به بخش‌های کوچک‌تر و تخصصی‌تر تقسیم شوند

به‌عبارت دیگر، اینترفیس‌های بزرگ و حجیم نباید کلاس‌های مختلف را مجبور به پیاده‌سازی متدهایی کنند که نیازی به آن‌ها ندارند. در عوض باید اینترفیس‌های بزرگ را به اینترفیس‌های کوچک‌تر و تخصصی‌تر تقسیم کنیم. این‌گونه هر کلاس مجبور است فقط متدهایی را پیاده‌سازی کند که واقعاً به آن‌ها نیاز دارد.

این اصل یکی از اصول SOLID است. توجه کنید که به آن اصل (principle) می‌گوییم و نه یک قانون (rule)! بنابراین الزامی به رعایت جداسازی اینترفیس‌ها نداریم اما بهتر است به‌خاطر مزایایی که دارد، آن را در طراحی نرم‌افزار در نظر بگیریم.

کدهایی که برای مثال در این آموزش نوشته‌ام به زبان PHP هستند. اما این مفاهیم به‌طور کاملاً یکسان در تمام زبان‌های شیءگرای دیگر نیز قابل پیاده‌سازی هستند. بنابراین با هر زبانی که کار می‌کنید می‌توانید از این آموزش استفاده کنید.

مثال استفاده از اصل Interface Segregation

فرض کنید در حال طراحی و پیاده‌سازی یک سیستم فروشگاهی هستیم. این فروشگاه روش‌های مختلفی برای پرداخت مبلغ فاکتور به کاربران خود ارائه می‌دهد. مثلاً:

  • پرداخت از طریق درگاه‌های بانکی
  • پرداخت اعتباری (credit card)
  • پرداخت از طریق کیف پول

از آن‌جا که هر کدام از این روش‌ها ممکن است از راه‌های مختلفی پیاده شوند. یعنی خودِ پرداخت درگاه بانکی ممکن است از طریق بانک ملت، بانک سامان یا هر بانک دیگری که می‌خواهیم انجام شود. در چنین مواقعی بهتر است یک interface برای آن‌ها در نظر بگیریم. دقیقاً مشابه کاری که در آموزش اصل Open Closed انجام دادیم.

اولین ایده‌ای که احتمالاً به ذهنمان می‌رسد این است که یک اینترفیس برای روش‌های پرداخت در نظر بگیریم. این اینترفیس (رابط) نام متدهایی که برای هر نوع پرداخت لازم داریم را مشخص می‌کند.

<?php
 interface PaymentGatewayInterface {
     public function process_bank_online_payment();

     public function process_credit_card_payment();

     public function process_wallet_payment();
 }

در قطعه کد بالا، سه متد به‌ترتیب برای روش‌های پرداختی که بالاتر گفتم در نظر گرفته‌ام. حال اگر بخواهیم یک کلاس برای درگاه بانک سامان ایجاد کنیم، باید کدی شبیه به زیر بنویسیم:

<?php
 class SamanGateway implements PaymentGatewayInterface {
 
     public function process_bank_online_payment(){
         // کدهای پرداخت از طریق درگاه بانک
     }

     public function process_credit_card_payment(){
         // این درگاه نیازی به این متد ندارد
     }

     public function process_wallet_payment(){
         // این درگاه نیازی به این متد ندارد
     }
 
 }

در این کلاس PHP، مجبوریم متدهای دوم و سوم (پرداخت اعتباری و کیف پول) را حتماً پیاده‌سازی کنیم. چرا؟ چون از مفاهیم برنامه نویسی شیءگرا می‌دانیم که وقتی از یک اینترفیس پیروی می‌کنیم، مجبوریم تمام متدهایی که در اینترفیس تعریف شده‌اند را پیاده‌سازی کنیم. اما می‌بینید که در کلاس درگاه پرداخت بانکی، اصلاً نیازی به این متدها نداریم.

مثال انتزاعی و تصویری از اصل ISP یا جداسازی اینترفیس در برنامه‌نویسی
مثال انتزاعی و تصویری از اصل ISP یا جداسازی اینترفیس در برنامه‌نویسی

اصلاح کد با رعایت اصل تفکیک اینترفیس ها

برای رعایت اصل Interface Segregation باید اینترفیس فعلی (که بزرگ است) را به اینترفیس‌های کوچک‌تر تقسیم کنیم.

در مثالی که در این آموزش زدم، سه اینترفیس کوچک‌تر خواهیم داشت که هر کدام حاوی یک متد هستند.

<?php
 interface BankOnlinePaymentInterface {

     public function process_bank_online_payment();

 }

 interface CreditCardPaymentInterface {

     public function process_credit_card_payment();

 }

 interface WalletPaymentInterface {

     public function process_wallet_payment();

 }

حالا اگر بخواهیم درگاه پرداخت آنلاین یک بانک را پیاده‌سازی کنیم، با پیروی از اینترفیس BankOnlinePaymentInterface فقط متدهایی که لازم داریم را پیاده‌سازی می‌کنیم.

<?php
 class MellatGateway implements BankOnlinePaymentInterface {
 
     public function process_bank_online_payment(){
         // کدهای پرداخت از طریق درگاه بانک
     }
 
 }

این‌طوری هر کلاس کاملاً مستقل است و فقط متدهای مرتبط با خودش را پیاده‌سازی می‌کند. این نوع طراحی، انعطاف‌پذیری بیشتری در کدها و توسعه‌های آینده فراهم می‌کند.

توجه کنید که در این مثال، برای سادگی و کاهش پیچیدگی کدها، هر اینترفیس کوچک‌تر حاوی یک متد شد. در حالی که در دنیای واقعی معمولاً اینترفیس‌ها دارای چند متد هستند. پس حواستان باشد که اینکه فقط یک متد در هر interface داریم مربوط به مثال است و جزء اصل جداسازی اینترفیس‌ها نیست.

بهبود کد interfaceها

در مثالی که زدم، نام متدها در اینترفیس‌های مختلف (که همگی مربوط به روش پرداخت هستند) متفاوت هستند. معمولاً پیشنهاد می‌شود که نام متدهایی که یک کار خاص را انجام می‌دهند در این اینترفیس‌ها یکسان باشد.

برای مثال، همه interfaceها متدی دارند که برای انجام روند پرداخت است. بنابراین بهتر است نام متدها در اینترفیس‌ها را به‌صورت یکسان تعریف کنیم. مثلاً:

<?php
 interface BankOnlinePaymentInterface {
     public function process();
 }

 interface CreditCardPaymentInterface {
     public function process();
 }

 interface WalletPaymentInterface {
     public function process();
 }

البته این موضوع بستگی به نحوه طراحی، نیازهای سیستم و نیز قراردادهای تیم توسعه پروژه دارد.

بهتر است نام متدهایی که در اینترفیس‌های روش‌های پرداخت تعریف می‌شوند یکسان یا شبیه به هم باشند. این مسئله باعث بهبود کدها در مفهوم «پلی‌مورفیسم / Polymorphism» می‌شود. همچنین در راستای رعایت اصل Open Closed از اصول SOLID نیز خواهد بود.

اصل Liskov Substitution در برنامه‌نویسی به زبان ساده

اصل Liskov Substitution در برنامه‌نویسی به زبان ساده

چرا اصل Interface Segregation اهمیت دارد؟

به زبان خیلی ساده می‌توانم بگویم که با رعایت این اصل، کلاس‌های ما متدهای اضافی که نیازی به آن‌ها ندارند را پیاده‌سازی نمی‌کنند. در نتیجه پیچیدگی‌های اضافی در کدهای ما وجود ندارد. به‌طور کلی سه مزیت اصلی برای رعایت اصل جداسازی اینترفیس‌ها (=تفکیک اینترفیس) در نظر می‌گیرند:

  • کاهش پیچیدگی: همان‌طور که پیش‌تر مثال زدم، با کوچک‌تر شدن اینترفیس‌ها و عدم نیاز به پیاده‌سازی متدهایی که لازم نداریم، وابستگی‌های غیرضروری بین کلاس‌ها و کدهای ما حذف می‌شوند.
  • توسعه آسان‌تر: وقتی وابستگی کمتری در کدها داریم، تغییرات در سیستم با ریسک کمتری انجام می‌شود. (اصطلاحاً می‌گوییم هزینه توسعه و نگهداری کاهش پیدا می‌کند)
  • تست‌پذیری بهتر: با کاهش وابستگی‌ها و پیچیدگی‌های کد، تست‌های ساده‌تر و متمرکزتری در تست‌نویسی کلاس‌ها داریم.

مثال دیگر برای این اصل

فرض کنید می‌خواهیم بخش مدیریت سفارش‌های یک سیستم نرم‌افزاری را بنویسیم. به اینترفیس زیر توجه کنید و فکر کنید که چه مشکلی دارد و بهتر است چطور آن را تغییر دهیم. سپس ادامه توضیحات را بخوانید.

<?php
 interface OrderManagementInterface {
     public function create_order();     // ایجاد سفارش
     public function cancel_order();     // لغو سفارش
     public function track_shipment();   // پیگیری ارسال سفارش
 }

در این مثال بهتر است متد پیگیری ارسال سفارش را در اینترفیس جداگانه‌ای قرار دهیم. در نگاه اول می‌توان از نظر معنایی آن را توجیه کرد. چون روند ارسال سفارش کمی نامرتبط با ثبت و لغو سفارش است.

اگر از دیدگاه اصل Interface Segregation به مسئله نگاه کنیم، چون که ممکن است در این سیستم، محصول مجازی که نیازی به ارسال ندارد هم داشته باشیم، این جداسازی اینترفیس ضروری است. چرا که سفارش محصولات مجازی امکان ثبت و لغو دارند اما پیگیری مرسوله برایشان معنی ندارد.

<?php
 interface OrderManagementInterface {
     public function create();
     public function cancel();
 }

 interface ShipmentTrackingInterface {
     public function track();
 }

به‌عنوان مرور، اصل جداسازی رابط‌ها یا Interface Segregation می‌گوید که هر اینترفیس فقط باید چیزهایی که کلاس‌هایی که از آن پیروی می‌کنند نیاز دارند را ارائه دهد؛ نه بیشتر و نه کمتر.

جمع‌بندی آموزش

در این آموزش با اصل جداسازی اینترفیس‌ها (یا تفکیک اینترفیس‌ها / Interface Segregation) که به اختصار اصل ISP نیز گفته می‌شود آشنا شدیم. وقتی یک اینترفیس بزرگ طراحی می‌کنیم، ممکن است کلاس‌ها مجبور شوند متدهایی را پیاده‌سازی کنند که بلااستفاده است. این وابستگی‌های غیرضروری باعث افزایش پیچیدگی کد و افزایش هزینه توسعه می‌شود. این اصل به‌طور خلاصه به این مسئله می‌پردازد که اینترفیس‌های بزرگ نباید کلاس‌های مختلف را مجبور به پیاده‌سازی متدهایی کنند که نیازی به آن‌ها ندارند.

برای طراحی اصولی، بهتر است نیاز واقعی کلاس‌ها را در نظر بگیرید. اینترفیس‌ها را به گونه‌ای طراحی کنید که فقط شامل متدهای مرتبط باشند. البته این قضیه یک پیشنهاد است که بهتر است برای داشتن کدهای تمیزتر با پیچیدگی کمتر، آن را رعایت کنیم؛ اما اجباری نیست.

اگر با طراحی دیاگرام‌های UML آشنایی دارید، می‌توانید Class Diagramهای این موضوع را در این (+) منبع انگلیسی بررسی کنید. امیدوارم از این آموزش استفاده لازم را برده باشید. اگر سؤال یا چالشی دارید از بخش دیدگاه‌های همین آموزش مطرح کنید.