چند ریختی (Polymorphism)
Polymorphism یک کلمه یونانی به معنای "یک نام چند شکل" است. به عبارت دیگر، یک شیء دارای اشکال متعدد یا دارای یک نام با چندین عملکرد است. poly به معنای بسیاری و morph به معنای اشکال است. Polymorphism این قابلیت را برای یک کلاس فراهم می کند که چندین پیاده سازی با یک نام داشته باشد. این یکی از اصول اصلی برنامه نویسی شیء گرا پس از کپسوله سازی و وراثت است.
انواع پلی مورفیسم در سی شارپ
دو نوع پلی مورفیسم در سی شارپ وجود دارد:
1- چند ریختی زمان کامپایل/ استاتیک (Static / Compile Time Polymorphism)
2- چند ریختی زمان اجرا / پویا (Dynamic / Runtime Polymorphism)
چند ریختی زمان کامپایل/ استاتیک
چند ریختی زمان کامپایل به عنوان Early Binding نیز شناخته می شود. Method overloading نمونه ای از چند ریختی استاتیک یا Static Polymorphism است. در overloading، متد / تابع همان نام را دارد اما نتایج و اثرات متفاوتی دارد. این روش به عنوان چند ریختی زمان کامپایل Compile Time Polymorphism نیز شناخته می شود زیرا تصمیم گیری در مورد اینکه کدام متد باید فراخوانی شود در زمان کامپایل گرفته می شود. overloading مفهومی است که در آن نام متد ها با مجموعه ای از پارامترهای متفاوت یکسان است. در این روش کامپایلر سی شارپ تعداد پارامترهای ارسال شده و نوع پارامتر را بررسی می کند و تصمیم می گیرد که کدام متد را فراخوانی کند و اگر متد منطبقی پیدا نشد، خطا می دهد. در مثال زیر، یک کلاس دارای دو متد با همان نام "Add" اما با پارامترهای ورودی متفاوت است (روش اول سه پارامتر و روش دوم دو پارامتر دارد).
همانطور که گفته شد Compile-time polymorphism در زبان C# وجود چندین متد با نام یکسان، اما با آرگومان های متفاوت در نوع و/یا تعداد است که به آن overloading متد نیز گفته می شود. ما می توانیم از آن در شرایطی استفاده کنیم که نیاز به پیاده سازی چندین روش با عملکرد مشابه داریم و تصمیم می گیریم که نام یکسانی به آنها بدهیم. مثال زیر را مشاهده کنید.
در این مثال، کلاس Logger سه متد را تعریف می کند و از LogLevels enum استفاده می کند:
متد دوم یک مقدار پیش فرض (Info) را برای log level فراهم می کند. متد سوم یک عدد صحیح را به عنوان ورودی می پذیرد و آن را به log level مربوطه تبدیل می کند. هر دو متد در نهایت از اولی استفاده می کنند تا پیام را در فایل log بنویسند.
operator overloading یک مورد خاص از method overloading است. در اینجا می توانیم برای هر متد اپراتور چندین پیاده سازی تعریف کنیم. همچنین، سازنده ها اغلب بیش از حد بارگذاری می شوند. می توانیم سازنده هایی با پارامترهای متعدد و همچنین سازنده پیش فرض داشته باشیم.
دلیل دیگر استفاده از method overloading زمانی است که باید تعریف یک متد را با افزودن یک یا چند پارامتر به آن تغییر دهیم. از آنجایی که این تغییر احتمالاً کدهای موجود را خراب می کند، گاهی اوقات ترجیح داده می شود که متد جدیدی معرفی شود که متد اولیه را overload کند.
چند ریختی زمان اجرا / داینامیک
پلی مورفیسم زمان اجرا / داینامیک (Dynamic / runtime polymorphism) به عنوان late binding نیز شناخته می شود. در اینجا تعداد پارامترها و نوع پارامتر باید یکسان باشد و ممکن است پیاده سازی متفاوتی داشته باشد. method overriding نمونه ای از چند ریختی پویا است.
با استفاده از وراثت می توان method overriding را انجام داد. با method overriding ، این امکان وجود دارد که کلاس پایه (base class) و کلاس مشتق شده (derived class) نام متد یکسان و چیزی مشابه داشته باشند. کامپایلر از متد موجود برای overriding عملکرد آگاه نیست، بنابراین کامپایلر در زمان کامپایل خطایی ایجاد نمی کند. کامپایلر تصمیم می گیرد که در زمان اجرا کدام متد را فراخوانی کند و اگر متدی پیدا نشد، خطا می دهد. در ادامه مثالی در این زمینه به همراه خروجی نشان داده شده است.
خروجی قطعه کد بالا در زیر نشان داده شده است.
در مثال بالا، یک کلاس پایه به نام Interest و دو کلاس مشتق شده با نام های SimpleInterest و FixedInterest ایجاد کردیم. در کلاس پایه از کلمه کلیدی virtual با متد استفاده کردیم تا با استفاده از کلمه کلیدی override در کلاس مشتق شده override شود. در اینجا، ما همان نام متد را که TrueBank است و signature یکسانی (پارامترهای تعداد و نوع) را ارائه کرده ایم، اما در کلاس های مشتق شده/فرزند، تعاریف متدهای متفاوتی را ارائه کرده ایم.
ما شیء کلاس Interest را ایجاد کردیم و لیست پارامترها را ارائه کردیم. اگر لیست پارامترهای بالا را از نزدیک مشاهده کنید، متوجه خواهید شد که پارامترهای مشابهی برای هر فراخوانی متد ارائه شده است. در اینجا کامپایلر برای کامپایل موفقیت آمیز فقط به متد TrueBank() نیاز دارد و در زمان اجرا متدهای مورد نظر به ترتیب فراخوانی می شوند، بر اساس اینکه کدام شیء کلاس آن را فراخوانی می کند.