مقدمه
پس از کپسولهسازی و وراثت، به سومین ستون اصلی برنامهنویسی شیءگرا (OOP) یعنی چندریختی
(Polymorphism) میرسیم. کلمهی پلیمورفیسم از زبان یونانی گرفته شده و به معنای
"داشتن چندین شکل" است. در برنامهنویسی، چندریختی به این معناست که یک متغیر، پارامتر یا کالکشن
بتواند اشیائی از انواع مختلف را در خود نگه دارد و رفتار متناسب با نوع واقعی آن شیء در زمان اجرا
از خود نشان دهد. این قابلیت که به طور مستقیم بر پایهی وراثت و متدهای virtual و override بنا
شده، به ما اجازه میدهد کدهایی بسیار انعطافپذیر، قابل توسعه و تمیز بنویسیم.
یک رابط، چندین پیادهسازی
ایدهی اصلی چندریختی این است که شما میتوانید با مجموعهای از اشیاء متفاوت از طریق یک رابط مشترک
(کلاس پایه) کار کنید، بدون اینکه نیاز به دانستن نوع دقیق هر شیء داشته باشید. بیایید به مثال کلاس
Animal از درسهای قبل برگردیم. ما یک کلاس پایهی Animal با یک متد virtual به نام
MakeSound داشتیم و کلاسهای Dog و Cat این متد را override کرده بودند.
Program.cs
public class Animal
{
public virtual void MakeSound() { Console.WriteLine("Generic animal sound"); }
}
public class Dog : Animal
{
public override void MakeSound() { Console.WriteLine("Woof!"); }
}
public class Cat : Animal
{
public override void MakeSound() { Console.WriteLine("Meow!"); }
}
اکنون "جادوی" چندریختی خود را نشان میدهد. از آنجایی که هم Dog و هم Cat یک نوع Animal
هستند (رابطهی "Is-A")، ما میتوانیم آنها را در یک کالکشن از نوع Animal قرار دهیم.
Program.cs
var animals = new List<Animal>
{
new Cat(),
new Dog(),
new Cat(),
new Animal()
};
foreach (Animal a in animals)
{
a.MakeSound();
}
خروجی این کد به شکل زیر خواهد بود:
Meow!
Woof!
Meow!
Generic animal sound
به این رفتار دقت کنید. با وجود اینکه متغیر حلقه (a) از نوع Animal است، اما در هر بار تکرار،
.NET به نوع واقعی شیء در زمان اجرا نگاه میکند و نسخهی صحیح و
بازنویسی شدهی متد MakeSound را فراخوانی میکند. این فرآیند که در آن تصمیمگیری برای اجرای متد
به زمان اجرا موکول میشود، اتصال دیرهنگام (Late Binding) یا ارسال پویا
(Dynamic Dispatch) نامیده میشود.
چندریختی با کلاسهای انتزاعی
چندریختی زمانی قدرتمندتر میشود که با کلاسهای انتزاعی ترکیب شود. در درس قبل دیدیم که یک کلاس
انتزاعی، کلاس پایهای است که نمیتوان از آن نمونه ساخت و میتواند دارای اعضای انتزاعی (بدون
پیادهسازی) باشد. این الگو، کلاسهای مشتقشده را مجبور میکند تا پیادهسازی خود
را برای آن اعضا فراهم کنند.
بیایید مثال Shape از درس قبل را در نظر بگیریم.
Program.cs
public abstract class Shape
{
public abstract void Draw();
}
public class Circle : Shape
{
public override void Draw() { Console.WriteLine("Drawing a circle: O"); }
}
public class Rectangle : Shape
{
public override void Draw() { Console.WriteLine("Drawing a rectangle: []"); }
}
با این ساختار، ما میتوانیم یک متد بنویسیم که با هر نوع Shape کار کند، بدون اینکه نگران نوع
دقیق آن باشیم. ما تضمین داریم که هر شیئی که از نوع Shape باشد، حتماً یک متد
Draw قابل فراخوانی دارد.
Program.cs
var shapes = new List<Shape>
{
new Circle(),
new Rectangle(),
new Circle()
};
foreach (Shape s in shapes)
{
s.Draw();
}
خروجی به صورت زیر خواهد بود:
Drawing a circle: O
Drawing a rectangle: []
Drawing a circle: O
این الگو بسیار قدرتمند است. تصور کنید در آینده بخواهیم یک کلاس Triangle به برنامه اضافه کنیم.
تنها کاری که باید انجام دهیم تعریف کلاس Triangle و override کردن متد Draw است. کد حلقهی
foreach ما بدون هیچ تغییری، به درستی با اشیاء Triangle نیز کار خواهد کرد. این یعنی
قابلیت توسعهی بالا.
مزایای چندریختی
- قابلیت توسعه (Extensibility): به راحتی میتوانید انواع جدیدی را به سیستم
اضافه کنید بدون اینکه کدهایی که از کلاس پایه استفاده میکنند، نیاز به تغییر داشته باشند.
- انعطافپذیری و استفاده مجدد از کد: میتوانید متدهایی بنویسید که با یک نوع
پایه کار میکنند و آنها را برای تمام انواع مشتقشده نیز به کار ببرید.
- کد تمیزتر و سادهتر: چندریختی نیاز به بلوکهای طولانی if-else یا switch
برای بررسی نوع شیء را از بین میبرد و کد را خواناتر میکند.
به جای نوشتن کدی مانند زیر:
if (obj is Dog) { ... }
else if (obj is Cat) { ... }
شما به سادگی متد مورد نظر را فراخوانی میکنید و اجازه میدهید چندریختی کار خود را انجام دهد. این
یکی از زیباترین و قدرتمندترین جنبههای برنامهنویسی شیءگرا است.