اصل قابلیت جانشینی در طراحی کلاس ها – LSP

سامان

LSP مخفف عبارت Liskov Substitutability Principle می باشد و منظور از آن ارایه قانونی است که هنگام طراحی کلاس ها باید رعایت شود به گونه ای که بتوان instance های کلاس وارث را به جای instance های کلاس پایه در برنامه به کار برد. بنابراین می توان از آن به عنوان یک راهنما هنگام نوشتن کلاس های جدید استفاده کرد.

همانطور که می دانید وراثت یک مفهوم کلیدی در برنامه نویسی شی گراست. با کمک وراثت می توان یک رفتار اساسی و پایه برای یک مجموعه کلاس که همگی نیار به انجام این رفتار پایه را دارند، ایجاد نمود.

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

فرض کنید دو کلاس به نام های General و Special داریم که Special از General ارث برده است:‌

همانطور که می بینید هر دو کلاس دارای متدی به نام PrintReport می باشند ولی‌ فرض می کنیم از لحاظ ماهیت عملکرد ،‌ این دو متد یکسان نیستند و رفتار متفاوتی از خود نشان می دهند.

با توجه به آنچه که در مورد قوانین مربوط به وراثت گفته شد می توان نوشت:‌

اکنون فرض کنید کلاس دیگری به نام Report داریم که دارای متدی به نام Reporter می باشد که به صورت public و static است.

حال اگر بخواهیم از متد Reporter از کلاس Report استفاده کنیم با توجه به static بودن متد می توان نوشت:‌

با اجرای این دستور مشخص است که متد PrintReport مربوط به generalObj که object ای از کلاس General میباشد اجرا می شود. همانطور که قبلاً‌ اشاره شد،‌ می توانیم object ای از کلاس مشتق شده را به reference ای از کلاس پایه نسبت دهیم بنابراین می توانیم object ای از کلاس Special را به عنوان آرگومان به متد Reporter ارسال نماییم. و مشکل دقیقاً‌از همین جا آغاز می شود. مشکلی که LSP سعی در جلوگیری از وقوع آن را دارد. برای توضیح بیشتر اگر بنویسیم:‌

با اجرای این دستور متد PrintReport از specialObj که object ای از کلاس Special می باشد اجرا می گردد. ولی می دانیم که PrintReport ‌مربوط به کلاس General و Special دو عملکرد متفاوت از خود نشان می دهند. بنابراین رفتار برنامه هنگامی که یک instance از کلاس پایه را به عنوان آرگومان به متد Reporter می فرستیم در مقایسه با حالتی که یک instance از کلاس مشتق شده را به Reporter ارسال می نماییم متفاوت خواهد بود و این یعنی کلاس مشتق شده نمی تواند جایگزین مناسبی برای کلاس پایه باشد.

توضیح:‌ در این مقاله کوتاه سعی شده است LSP ،‌ ابتدا در حالت کلی و صرف نظر از ویژگی های زبان های برنامه نویسی شرح داده شود و سپس در ادامه نحوه وقوع آن در زبان C# مورد بررسی قرار گرفته است. دستوری که در بالا بدان اشاره شد به دلیل وقوع Method Hiding در زبان C# ،‌ منجر به نقض LSP نمی شود و استفاده از قطعه کد مربوطه تنها به منظور بیان مفوم LSP و جلب توجه خواننده به این نکته است که جایگزینی کلاس وارث به جای کلاس پایه می تواند منجر به ایجاد مشکل در عملکرد برنامه شود.

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

در زبان C# می توانیم در دو حالت متد های تکراری که دارای امضای یکسانی هستند را هم در کلاس پایه و هم در کلاس مشتق شده داشته باشیم. یک حالت این است که از روش Method Hiding استفاده کنیم به این صورت که متدی با نام و امضای یکسان هم در کلاس پایه و هم در کلاس وارث داشته باشیم. البته در این حالت کامپایلر به منظور اعلام Method Hiding یک warning می دهد که می توان برای اینکه مشخص شود اینکار آگاهانه صورت می گیرد از کلمه کلیدی new در اعلان متد استفاده کنیم.

نکته ای که وجود دارد این است که حتی اگر به جای ارسال object ای از نوع General به متد Reporter ،‌ یک object از نوع Special بفرستیم ،‌ از آنجایی که متد تکراری بر اساس Method Hiding است ،‌ در هر صورت متد PrintReport مربوط به General اجرا می گردد. بنابراین عدم یکسان بودن رفتار متد های PrintReport منجر به رفتار متفاوت برنامه نمی گردد لذا اصل LSP‌ در این حالت نقض نمی شود.

حالت دیگر در مورد متد های تکراری این است که متدی که در کلاس پایه تعریف شده است ،‌ به صورت virtual “و” متدی که در کلاس وارث است به صورت override تعریف شده باشد (متد virtual در کلاس وارث override شود). در این وضعیت اگر object ای از نوع Special‌ را به Reporter‌ ارسال نماییم ،‌ متد PrintReport مربوط به Special اجرا می گردد در صورتی که اگر یک instance از نوع General ارسال می شد ،‌ متد PrintReport مربوط به General اجرا می شد. بنابر این در این حالت یکسان نبودن رفتار متد های PrintReport هنگامی که یک object کلاس وارث به جای یک object کلاس پایه مورد استفاده قرار می گیرد منجر به متفاوت شدن رفتار برنامه و در نتیجه نقض شدن اصل LSP می گردد. کاری که برای جلوگیری از این وضعیت می توان انجام داد این است که هنگام override کردن متد virtual ماهیت عملکرد متد متفاوت نباشد،‌ هر چند که طبیعی است با توجه شرایط کلاسی که درون آن override شده است سفارشی شده باشد. قابل توجه است که اگر object ای از نوع کلاس وارث به صورت ضمنی یا صریح به object ای از نوع کلاس پایه تبدیل شود،‌ تنها در صورتی می تواند از متد های تکراری خودش استفاده کند که این متد ها override شده باشند و به صورت Method Hiding نباشند.

2
اشتراک گذاری
سامان
سامان

فارغ التحصیل کارشناسی نرم افزار، علاقه مند به برنامه نویسی، طراحی وب، تکنولوژی های نوین، یادگیری و فیلم

پاسخ دهید