اصول SOLID به زبان ساده - اصل اول
اصول SOLID به زبان ساده - اصل دوم
سومین اصل از اصول SOLID،اصل جایگزینی لیسکوف یا Liskov Substitution Principle هست که به اختصار LSP گفته میشه.
این اصل خیلی ساده هست. هم درک کردنش و هم پیاده سازیش. تعریف آکادمیک این اصل بصورت زیر هست:
اگر S یک زیر کلاس از T باشه، آبجکتهای نوع T باید بتونن بدون تغییر دادن کد برنامه با آبجکتهای نوع S جایگزین بشن
فرض کنیم یک کلاس داریم به اسم A:
class A { ... }
قراره از کلاس A آبجکتهایی ساخته بشه که توی جاهای مختلف برنامه استفاده کنیم. فرض کنیم کد زیر قسمتهای مختلف برنامه هست که داره از کلاس A استفاده میکنه:
x = new A; // ... y = new A; // ... z = new A;
حالا قراره کلاس A رو توسعه بدیم. برای همین کلاسی به اسم B رو میسازیم که از کلاس A مشتق میشه:
class B extends A { ... }
پس کلاس B، یک زیر نوع از کلاس A هست.
بالاتر دیدیم که توی برنامه، از کلاس A آبجکتهایی ساخته و استفاده شد. چون کلاس B یک زیر نوع از کلاس A هست، میخوایم توی برنامه و جایی که از کلاس A استفاده کردیم، بجای کلاس A، از کلاس B استفاده کنیم. یعنی:
x = new A new B; // ... y = new A new B; // ... z = new A new B;
اینجا ما جایگزینی انجام دادیم! کلاس B رو با کلاس A عوض کردیم. طبق اصل LSP، وقتی جایگزینی انجام میدیم، برنامه نباید بخاطر جایگزینی دچار خطا بشه. همچنین کد برنامه هم نباید تغییر کنه. این اصل به همین سادگی هست.
بیاید این قانون رو نقض کنیم تا اون رو بهتر متوجه بشیم. فرض کنیم یک کلاس داریم به اسم Note. این کلاس عملیات مختلفی انجام میده، مثل خواندن، بروزرسانی و حذف یادداشتهای شخصی :
class Note { public constructor(id) { // ... } public save(text): void { // save process } }
حالا یک کاربر میخواد از این کلاس توی برنامهی خودش استفاده کنه:
let note = new Note(429); note.save("Let's do this!");
خب میخوایم این کلاس رو توسعه بدیم. قراره یک ویژگی اضافه کنیم که بشه یادداشتهای فقط خواندنی ساخت. یعنی باید متد save رو رونوشت کنیم و اجازه ندیم عملیات ذخیره کردن یادداشت انجام بشه. برای این کار یک زیرکلاس از Note میسازیم و اسم اون رو میذاریم ReadonlyNote
و متد save رو رونوشت میکنیم:
class ReadonlyNote extends Note { public save(text): void { throw new Error("Can't update readonly notes"); } }
در حالی که متد save توی کلاس اصلی به کاربر void برمیگردوند، توی کلاس جدید یک Exception برمیگردونیم که به کاربر بگیم عملیات save ممکن نیست.
خب توی برنامه، اونجایی که از Note استفاده کردیم، یک جایگزینی انجام میدیم. یعنی بجای Note از ReadonlyNote استفاده میکنیم:
let note = new ReadonlyNote(429); note.save("Let's do this!");
خب چه اتفاقی میوفته؟
درحالی که کاربر بی اطلاع از تغییراتِ رخ داده هست، ناگهان یک چیز غیرمنتظره و یک Exception توی برنامهش رخ میده! که به ناچار باید یک سری تغییرات توی برنامه خودش اعمال کنه.
اینجا اصل LSP نقض شد. چون کلاس ReadonlyNote، رفتار و ویژگیهای کلاس والد رو تغییر داد که کاربر مجبور میشه کد برنامهش رو تغییر بده.
برای اینکه این قسمت رو بهتر بنویسیم، یک کلاس جدا میسازیم برای یادداشتهای قابل نوشتن. اسم کلاس رو میذارم WritableNote. یعنی یادداشتهایی که قابلیت بروزرسانی رو دارن و بعد متد save رو از کلاس Note به کلاس جدید منتقل کنیم:
class Note { public constructor(id) { // ... } } class WritableNote extends Note { public save(text): void { // save process } }
پس باید در نظر داشته باشیم وقتی که میخوایم یک کلاس رو با مشتق کردن توسعه بدیم، جاهایی از برنامه که از کلاس والد استفاده شده، باید بتونه بدون مشکل با کلاسهای فرزند هم کار کنه. یعنی کلاس فرزند نباید ویژگیها و رفتار کلاس والد رو تغییر بده. مثلا اگه کلاس والد یک متد داره که خروجی اون عددی هست، کلاس فرزند نباید این متد رو جوری رونوشت کنه که خروجی آرایه باشه.