اصل سوم پیاده سازی SOLID با کدهای دارت برای فریم ورک Flutter

Liskov Substitution Principle
Liskov Substitution Principle

“اصل جایگزینی لیسکوف“ یا (Liskov Substitution Principle) یا به طور خلاصه (LSP) سومین اصل از SOLID است و می گوید که: "انواع مشتق شده باید کاملاً قابل جایگزینی برای انواع پایه خود باشند" برای درک بهتر آن به مثال زیر توجه کنید.

فرض کنید کلاسی به نام مشتری داریم و بیزینس ما چندین نوع مشتری دارد(مثلاً مشتری تلفنی و مشتری عبوری و مشتری آنلاین) . برای این منظور کلاسی پایه به اسم Customer می سازیم و همه انواع مشتری ها را ملزم به ارث بری از کلاس Customer می کنیم .

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

بنابراین، همه Sub کلاس‌ها باید به همان شیوه کلاس‌های پایه خود عمل کنند. عملکرد خاص Sub کلاس ممکن است متفاوت باشد، اما باید با رفتار مورد انتظار کلاس پایه مطابقت داشته باشد.

نهایتا باید کلاس پایه بتوانند با Sub کلاس‌ خود جایگزین شوند. به عبارتی دیگر کلاس والد بتوانند جایگزین کلاس فرزند خود شود.


برای اینکه مفهوم را در مثال عملی کاملتر متوجه شویم آن را به دو شیوه صحیح و غلط آن پیاده سازی میکنیم.

۱- مثال زیر LSP را نقض می کند.

بیایید یک مثال جدید بزنیم، فرض کنید که یک فروشگاه از ما میخواهد برای آن یک سیستم ثبت پرداخت پیاده سازی کنیم. بنابر این، ما برای شروع یک کلاس Payment ایجاد می کنیم .

class Payment {
  late int customerID;
  late double orderCost;

  void getMoneyByCrashier() {
    // logic code for payment in store.
  }
}

این فروشگاه قصد دارد پرداخت مشتریان حضوری را به صورت تحویل به صندوقدار داشته باشد. بنابراین، ما یک Sub کلاس جدید به نام InStorePayment از کلاس Payment ایجاد می‌کنیم.

class InStorePayment extends Payment {
  @override
  void getMoneyByCrashier() {
    // logic code for payment in store.
  }
}

بعد از مدتی، فروشگاه از ما می‌خواهد که برای مشتریان تلفنی نیز سیستم پرداخت را توسعه دهیم. اکنون، ما یک Sub کلاس جدید به نام TelephonePayment از کلاس Payment ایجاد می‌کنیم. اما، وقتی می‌خواهیم متد () getMoneyByCrashier را پیاده سازی کنیم، متوجه می‌شویم که مشتریان تلفنی نمی‌توان مبلغ را به صندوقدار تحویل دهند!

class TelephonePayment extends Payment {
  @override
  void getMoneyByCrashier() {
    /* Can't be implemented since
     * The Customer is not in Store.
    */
  }
}

اکنون که با مشکل از نزدیک روبرو شدیم و درک بهتری از موضوع داریم، بیایید این بار با رعایت اصل جایگزینی لیسکوف ، ایرادات برنامه فوق را برطرف کنیم.

۲- مثال زیر LSP را رعایت می کند.

برای حل مشکل، باید سلسله مراتب وراثت را اصلاح کنیم .بیایید یک لایه اضافی تعریف کنیم که کمک کند انواع پرداخت ها را بهتر مدیریت کنیم. کلاس‌های OfflinePayment و OnlinePayment وظایف سوپر کلاس Payment را بین خود تقسیم خواهند کرد. همچنین متد ()getMoneyByCrashier را به OfflinePayment منتقل می کنیم و در مرحله بعد، یک متد ()getMoneyOnlineOptions برای کلاس OnlinePayment ایجاد می کنیم. در نهایت، کدهای مربوطه به شکل زیر خواهد بود.

class Payment {
  late int customerID;
  late double orderCost;
}

abstract class OfflinePayment extends Payment {
  void getMoneyByCrashier();
}

abstract class OnlinePayment extends Payment {
  void getMoneyOnlineOptions();
}

class TelephonePayment extends OnlinePayment {
  @override
  void getMoneyOnlineOptions() {
    // logic code for payment online.
  }
}

class InStorePayment extends OfflinePayment {
  @override
  void getMoneyByCrashier() {
    // logic code for payment in store.
  }
}

همان طور که مشاهده می کنید، اکنون می‌توانیم از هر Sub کلاسی به جای Super کلاس آن استفاده کنیم. علاوه بر این کد شما : قابلیت استفاده مجدد را دارد ، نگهداری از آن ساده تر است و منجر به کاهش Coupling (یا به عبارتی وابستگی بین ماژول ها) شده است.


در مقالات بعدی جزئیات پیاده سازی هر بخش رو با کدهای دارت، با همدیگر بررسی می کنیم.

مقدمه ای بر SOLID

توضیح اصل اول : Single Responsibility Principle

توضیح اصل دوم : Open Closed Principle

توضیح اصل سوم : Liskov Substitution Principle

توضیح اصل چهارم : Interface Segregation Principle

توضیح اصل پنجم : Dependency Inversion Principle