شاید برایتان اتفاق افتاده باشد که شرکتی از شما خواسته کدهای برنامه نویس دیگری را تکمیل کنید. اگر شرکت در قبال تمیزی و طراحی کدهای توسعه دهندگان سابق خود حساسیت نداشته باشد، احتمالا از آن تجربه به عنوان یکی از خاطرات تلخ خود یاد خواهید کرد! کدهای کثیف، پر از بوی بد (Bad Smell) در همه جای خود است! البته اینجا شما با یک غذای گندیده مواجه نیستید!
بوی بد در برنامه نویسی اصطلاحا به اشتباهاتی گفته میشود که بعدها باعث بروز مشکلات بزرگتر در کد میشوند. وقتی از عبارت طراحی کد خوب استفاده میکنیم، منظورمان کدی است که بوی بد نداشته باشد (یا حداقل به میزان قابل تحملی بوی بد داشته باشد)!
برای دستیابی به یک کد خوشبو باید اصول طراحی کد خوب را رعایت کنیم. یکی از مجموعه اصولی که برای طراحی کد خوب باید آنها را مد نظر قرار دهیم به اصول SOLID معروف اند. اصول SOLID (بخوانید سالید) عبارت اند از:
Single Responsibility Principle یا اصل تک وظیفگی (SRP)
Open/Closed Principle یا اصل باز و بسته بودن (OCP)
Liskov Substitution Principle یا اصل جانشینی لیسکف (LSP)
Interface Segregation Principle یا اصل تفکیک Interface (ISP)
Dependency Inversion Principle یا اصل وارونه کردن وابستگی (DIP)
در این مطلب به معرفی اصل سوم یعنی اصل جانشینی لیسکف (Liskov Substitution Principle) میپردازیم. اصل جانشینی به یکی از مشکلترین اصول SOLID معروف است اما به هر حال ما در این مقاله آن را به سادهترین شکل ممکن به شما یاد میدهیم. پس نگران نباشید و تا آخر مقاله به دقت همراهمان باشید!
اصل جانشینی لیسکف چیست
اصل جانشینی لیسکف اولین بار در سال 1987 توسط خانم باربارا لیسکف در کنفرانسش با عنوان "انتزاع (Abstraction) داده" معرفی شد. او بعدها در مقاله ای که به همراه خانم ژانت وینگ منتشر کرد، اصل جانشینی را اینطور تعریف کرد:
"فرض کنید Φ(x) یک خاصیت آبجکت x از نوع کلاس T باشد. در این صورت با فرض اینکه S یک زیر مجموعه از نوع T باشد،Φ(y) باید برای آبجکت یا آبجکتهای y از نوع کلاس S به درستی عمل کند."
اگر کمی صادقانه صحبت کنیم، تعریف بالا بیشتر شبیه یک جور قاعده آزاردهنده جبری سخت است! اما به زبان ساده میتوانیم آن را اینگونه بیان کنیم:
اصل جانشینی لیسکف میگوید: آبجکتهای کلاسهای والد و فرزند، باید بتوانند بدون به وجود آمدن هیچ مشکلی در کدها به جای هم استفاده شوند.
نقض کردن این قانون درست مثل این است که بخوایم گربه ای را به جای شیر جا بزنیم! گربه نمیتواند غرش کند و شیر نمیتواند میو میو کند. بنابراین وراثت کلاس گربه از شیر احتمالا خیلی به آرمانهای اصل جانشینی نزدیک نیست!
چرا باید از اصل جانشینی استفاده کنیم
در ابتدای یادگیری برنامه نویسی شی گرا احتمالا همیشه به شما گفته شده هر زمان که رابطه بین دو موجودیت از نوع is-A باشد، نشانه آن است که باید از وراثت یا Inheritance استفاده کنید. اما واقعیت این است که این موضوع در همه مسائل درست از آب در نمیآید و استفاده همیشگی آن در بعضی موارد مشکلاتی به همراه دارد. اصل جانشینی برای این استفاده میشود که مطمئن شویم وراثت همیشه به درستی به کار رفته است.
برای درک بهتر موضوع اجازه بدهید یک مثال ساده بزنیم. فرض کنید در برنامه یک کلاس مستطیل و یک کلاس مربع داریم. به نظر شما رابطه بین این دو کلاس باید چگونه باشد؟ همانطور که میدانید مربع یک نوع مستطیل است. بنابراین با یک رابطه از نوع is-A مواجهیم. اما احتمالا چون به تازگی در یک مقاله بدبینانه مثل این یکی، خوانده اید که استفاده همیشگی از وراثت در این موارد بوی بد تولید میکند، از این که جواب بدهید وراثت میترسید!
اما فرض کنید یک برنامه نویس بی دقت برای حل این مسئله به سرعت از وراثت استفاده کرده باشد. میخواهیم متدی داشته باشیم که مساحت شکل را با استفاده از اتریبیوت طول و عرض آبجکت شکل محاسبه کند. اما مشکل اینجاست که مربع بر خلاف مستطیل برای محاسبه مساحت تنها به عرض احتیاج دارد. حال یا باید به کلاس مستطیل دست بریم و آن را برای قبول عذر مربعها سازگار کنیم و یا برنامه بخاطر بی مقدار ماندن اتریبیوت عرض در متد محاسبه مساحت با Exception مواجه شود. با اعمال روش اول اصل Open/Closed در SOLID را نقض کرده ایم و با اعمال روش دوم برنامه دارای اشکال است.
در این نوشته با اصل سوم از اصول SOLID در طراحی کد آشنا شدید. اصول SOLID مجموعه ای از پنج اصل پایه ای در شیوههای درست برنامه نویسی است. اصول SOLID به شما کمک میکنند از بوهای بد کد یا Code Smells اجنتاب کنید! با رعایت این اصول میتوانید کدهایی تمیز (Clean Code) بنویسید. تغییر در کدها و گسترش آنها با رعایت SOLID ساده، و هزینههای مالی و زمانی آن کاهش مییابد. پس میبینید که یادگیری تک تک این اصول برای یک برنامه نویس تا چه حد حیاتی است. بنابراین بهتر است حتما خواندن این نوع مقالات را جدی بگیرید! آیا شما تجربه ای از بوی بد ناشی از رعایت نکردن اصل جانشینی لیسکف داشته اید؟! از خواندن نظرات شما خوشحال میشویم!
۱۵ دیدگاه
مهدی۲۵ آبان ۱۴۰۰، ۱۱:۳۲
سلام و درود
به نظرم مثال اینکه گربه رو به جای شیر جا بزنیم کاملا غلطه
چون نه شیر یک نوع گربه هست
و نه گربه یک نوع شیر هست
بلکه هر دو یک گربه سان هستند
درواقع این درسته که ما یک کلاس گربه سان داشته باشیم و کلاس گربه و شیر هر دو از کلاس گربه سان ارث بری بکنن
Nazanin KarimiMoghaddam۲۶ آبان ۱۴۰۰، ۰۵:۲۵
درود
بنده هم با شما موافقم. بنظرم مثال مربع و مستطیل بهتر و دقیقتر هست.
ممنون که با ما همراه هستید.
علی۱۵ تیر ۱۴۰۰، ۱۲:۳۷
واقعا اسم این رو میذارید مقاله و توضیح. چهار خط صورت مسئله رو نوشتین و یک پاسخ کوتاه و ناقص و صد در صد اشتباه. واقعا ناامید شدم از سایتتون.
نازنین کریمی مقدم۰۴ مرداد ۱۴۰۰، ۱۰:۱۹
سلام
ممنون از توجه و همراهی شما
حقیقتا این سری مقالات همونطور که از تاریخشون مشخص هست کمی قدیمی هستند و در لیست ویرایش ما قرار دارند. اما از لحاظ محتوایی همونطور که در کامنتها بحث کردیم اشتباهی توشون نیست. دوست داشته باشید میتونیم روی اشتباهی که پیدا کردید صحبت کنیم.
حامد۰۳ اسفند ۱۳۹۹، ۰۹:۰۹
سلام خدا قوت .
بنظرم مثالتون اشتباه است.
در این مثال میتوان موافق با این اصل توسعه داد.
فقط باید در کلاس مربع فقط از پراپرتی عرض استفاده کرد و به پراپرتی طول والد هم در constructor مربع همان مقدار عرض را داد یا اینکه نهایتا متد مساحت را override کرد.
در این صورت اگر از کلاس مستطیل استفاده کنیم به مشکلی برنمیخوریم. مهم این است که نوع داده خروجی شان یک نوع باشد تا به ارور و خطا برنخوریم
نازنین کریمی مقدم۰۳ اسفند ۱۳۹۹، ۰۹:۳۰
درود خداوند بر شما
ما هم اکنون به این نقطه رسیدیم که خیلی از توسعه دهندگان هنوز نتونستند به توافقی توش برسند :)
اینجا یک گروه هستند که دقیقا عقیده شما رو دارند. تفکر اشتباهی هم نیست و چه بسا که نود درصد ما در عمل همین کار رو انجام میدیم.
گروه دوم سختگیرتر هستند؛ همین که این توابع نیاز به override دارند رو مبنی بر این میگیرند که اصل رعایت نشده. علاوه بر اون، معتقد هستند که استفاده از دو مقدار مشترک برای طول و عرض ممکن هست شفافیت رو از بین ببره و باعث خطا بشه.
علی۲۲ تیر ۱۳۹۹، ۱۲:۳۱
سلام
ممنون از مقاله مفیدتون واقعا به کارم اومد
ولی بنظرم مثال اخر اشتباه بیان شده چون داره متدی که از والد به ارث برده شده رو override میکنه
فائقه نامور۱۹ مرداد ۱۳۹۹، ۱۶:۲۴
سلام
ممنون از توجه و همراهی شما
موردی که اشاره فرمودید به تیم تولید محتوا انتقال داده میشود.
مجید زارع۳۱ خرداد ۱۳۹۹، ۰۶:۴۹
سلام خدا قوت.. ممنونم بابت مقاله خوبتان
یک سوالی که پیش میآید این است که اگر بتوانیم این دو کلاس را جای یکدیگر استفاده کنیم دیگر نیاز به کلاس فرزند نیست و میتوانیم از کلاس والد در همه جا استفاده کنیم پس چرا کلاس فرزند را بنویسیم؟
لطفا این سوال من را سریع جواب دهید. برام دغدغه فکری شده
نازنین کریمی مقدم۳۰ شهریور ۱۳۹۹، ۲۰:۲۱
سلام.
پاسخ شما رو اینطوری میتونم بدم که مثلا در مثال مساحت مستطیل و مربع که زده شد، ما یه متد برای محاسبه مساحت در کلاس والد داشتیم که به درد فرزند نمیخورد. تو این حالت برای اینکه برنامه اجرا بشه باید یا تابع مساحت والد رو اصلاح کنیم یا تابع مساحت فرزند رو. اینکه به اینجا رسیدیم یعنی این اصل رعایت نشده. برای اینکه این اصل رعایت بشه از اینترفیس استفاده میکنیم.
اینجا رابطه پدر فرزندی هنوز هست (از لحاظ منطقی و تعریف یسری ویژگیها)، فقط داریم تاکید میکنیم که هر تابعی که در کلاس والد داریم باید بی خطا تو کلاس فرزند اجرا شه.
اگه از فرزند استفاده نکنیم هم همونطور که گفتید همون نتیجه رو میده، اما ارتباط بین کلاسهامون از دست میره. کلا برنامه نویسی یک راه نداره و هرجایی میشه کلی کلاس در کنار هم تعریف کرد، هدف از تعریف این اصول اینه که اگر یه نفر کدمون رو خوند، راحتتر متوجه ارتباط کلاسها بشه.
۲۴ آبان ۱۴۰۱، ۱۶:۳۱
این اصل در واقع برای این هدف ایجاد شده تا رفتار فرزند خیلی از والد فاصله نگیرد و در مورد جایگزینی بهتر است گفته شود : "کلاس فرزند بتواند جایگزین کلاس والد شود" این یعنی رفتار فرزند از والد فاصله نگرفته.
امین کریمی۲۴ فروردین ۱۳۹۹، ۱۰:۴۷
ممنون از مقالات خوبتون
البته به نظرم این مقاله خیلی میتونه کاملتر بشه تا بهتر بشه این اصل رو درک کرد
منظورتون از مشکلات scalingچی هست میشه توضیح بدید؟
Amirhosein karimi۱۳ فروردین ۱۳۹۹، ۱۶:۵۶
سلام ،
ممنون از مقالات خوبی که منتشر میکنید.
مثالی که زدید فکر میکنم اشتباه
چون ما رفتار متد extract در کلاس SecondryEmailExtratctor بازنویسی ( override ) کردیم و از کلاس SecondryEmailExtratctor نمیتونم رفتار متد extract پدر را داشته باشیم .
مجید زادگی۳۱ اردیبهشت ۱۳۹۸، ۰۵:۳۳
سلام و عرض ادب
ممنون از مقالات خوبی که منتشر میکنید.
معمولا بیان و درک این اصول بسیار ساده به نظر میرسه اما خیلی کم میشه که این اصول رو رعایت کنیم، من این 5 اصل رو پرینت گرفتم و جلوی میز کارم گذاشتم تا همیشه حواسم باشه دارم چکار میکنم!
بسیار عالی
متشکرم
زهرا فرحمند۰۴ خرداد ۱۳۹۸، ۰۵:۵۸
سلام جناب زادگی خیلی خوشحالم که استفاده کردید. درسته واقعا رعایت این اصول میتونه از به هم ریختگیها و مشکلات Scaling بعدی جلوگیری کنه. موفق باشید :)