مقدمه

پس از کپسوله‌سازی و وراثت، به سومین ستون اصلی برنامه‌نویسی شیءگرا (OOP) یعنی چندریختی (Polymorphism) می‌رسیم. کلمه‌ی پلی‌مورفیسم از زبان یونانی گرفته شده و به معنای "داشتن چندین شکل" است. در برنامه‌نویسی، چندریختی به این معناست که یک متغیر، پارامتر یا کالکشن بتواند اشیائی از انواع مختلف را در خود نگه دارد و رفتار متناسب با نوع واقعی آن شیء در زمان اجرا از خود نشان دهد. این قابلیت که به طور مستقیم بر پایه‌ی وراثت و متدهای virtual و override بنا شده، به ما اجازه می‌دهد کدهایی بسیار انعطاف‌پذیر، قابل توسعه و تمیز بنویسیم.

یک رابط، چندین پیاده‌سازی

ایده‌ی اصلی چندریختی این است که شما می‌توانید با مجموعه‌ای از اشیاء متفاوت از طریق یک رابط مشترک (کلاس پایه) کار کنید، بدون اینکه نیاز به دانستن نوع دقیق هر شیء داشته باشید. بیایید به مثال کلاس Animal از درس‌های قبل برگردیم. ما یک کلاس پایه‌ی Animal با یک متد virtual به نام MakeSound داشتیم و کلاس‌های Dog و Cat این متد را override کرده بودند.

Copy Icon 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 قرار دهیم.

Copy Icon Program.cs
// A list that can hold any object derived from Animal.
var animals = new List<Animal>
{
    new Cat(),
    new Dog(),
    new Cat(),
    new Animal() // even a base Animal object
};

// Iterate through the list and call the same method on each object.
foreach (Animal a in animals)
{
    // Polymorphism in action! The correct method is called at runtime.
    a.MakeSound();
}

خروجی این کد به شکل زیر خواهد بود:

Meow!
Woof!
Meow!
Generic animal sound
                    

به این رفتار دقت کنید. با وجود اینکه متغیر حلقه (a) از نوع Animal است، اما در هر بار تکرار، .NET به نوع واقعی شیء در زمان اجرا نگاه می‌کند و نسخه‌ی صحیح و بازنویسی شده‌ی متد MakeSound را فراخوانی می‌کند. این فرآیند که در آن تصمیم‌گیری برای اجرای متد به زمان اجرا موکول می‌شود، اتصال دیرهنگام (Late Binding) یا ارسال پویا (Dynamic Dispatch) نامیده می‌شود.

چندریختی با کلاس‌های انتزاعی

چندریختی زمانی قدرتمندتر می‌شود که با کلاس‌های انتزاعی ترکیب شود. در درس قبل دیدیم که یک کلاس انتزاعی، کلاس پایه‌ای است که نمی‌توان از آن نمونه ساخت و می‌تواند دارای اعضای انتزاعی (بدون پیاده‌سازی) باشد. این الگو، کلاس‌های مشتق‌شده را مجبور می‌کند تا پیاده‌سازی خود را برای آن اعضا فراهم کنند.

بیایید مثال Shape از درس قبل را در نظر بگیریم.

Copy Icon Program.cs
public abstract class Shape
{
    // An abstract method MUST be overridden.
    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 قابل فراخوانی دارد.

Copy Icon Program.cs
// A collection of different shapes.
var shapes = new List<Shape>
{
    new Circle(),
    new Rectangle(),
    new Circle()
};

// Process all shapes polymorphically.
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) { ... }

شما به سادگی متد مورد نظر را فراخوانی می‌کنید و اجازه می‌دهید چندریختی کار خود را انجام دهد. این یکی از زیباترین و قدرتمندترین جنبه‌های برنامه‌نویسی شیءگرا است.