مقدمه
وارد فصل ششم و یکی دیگر از ارکان اصلی برنامهنویسی شیءگرا (OOP) یعنی وراثت
(Inheritance) میشویم. وراثت مکانیزمی است که به یک کلاس اجازه میدهد تا ویژگیها
(پراپرتیها) و رفتارها (متدها) را از کلاس دیگری به ارث ببرد. این قابلیت، استفاده مجدد از کد را
به شدت افزایش داده و به ما کمک میکند تا یک سلسله مراتب منطقی و طبیعی بین انواع دادهی خود ایجاد
کنیم. در این مدل، کلاسی که ویژگیهای خود را به اشتراک میگذارد، کلاس پایه (Base
Class) یا والد نامیده میشود و کلاسی که این ویژگیها را به ارث میبرد، کلاس
مشتقشده (Derived Class) یا فرزند نام دارد.
رابطهی "Is-A" (یک نوع از)
وراثت برای مدلسازی رابطهی "Is-A" یا "یک نوع از" به کار میرود. این یک آزمون
ساده برای تشخیص این است که آیا استفاده از وراثت در یک سناریو منطقی است یا خیر. برای مثال:
- یک Dog یک نوع Animal است.
- یک Car یک نوع Vehicle است.
- یک Manager یک نوع Employee است.
در تمام این موارد، کلاس مشتقشده (فرزند) یک نسخهی تخصصیتر از کلاس پایه (والد) است. او تمام
ویژگیهای عمومی والد را دارد، به علاوهی ویژگیها و رفتارهای مخصوص به خود.
تعریف کلاسهای پایه و مشتقشده
برای پیادهسازی وراثت در C#، پس از نام کلاس مشتقشده، از یک علامت دو نقطه (`:`) و سپس
نام کلاس پایه استفاده میکنیم. بیایید این مفهوم را با یک مثال کلاسیک از حیوانات پیادهسازی کنیم.
Program.cs
public class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine("Woof woof!");
}
}
public class Cat : Animal
{
public void Meow()
{
Console.WriteLine("Meow!");
}
}
در کد بالا، Animal کلاس پایه است. کلاسهای Dog و Cat هر دو از Animal ارثبری میکنند.
این بدان معناست که هر شیء از نوع Dog یا Cat به طور خودکار دارای پراپرتی Name و متد Eat
خواهد بود.
استفاده مجدد از اعضای کلاس پایه
اکنون که وراثت را برقرار کردیم، میتوانیم ببینیم که چگونه کلاسهای مشتقشده از اعضای کلاس پایه
استفاده میکنند.
Program.cs
Dog myDog = new Dog();
myDog.Name = "Buddy";
myDog.Eat();
myDog.Bark();
Cat myCat = new Cat();
myCat.Name = "Whiskers";
myCat.Eat();
myCat.Meow();
خروجی این کد به شکل زیر خواهد بود:
Buddy is eating.
Woof woof!
Whiskers is eating.
Meow!
همانطور که میبینید، ما بدون نیاز به تعریف مجدد Name یا Eat در کلاسهای Dog و Cat،
توانستیم از آنها استفاده کنیم. این قدرت استفاده مجدد از کد است که وراثت برای ما به ارمغان
میآورد.
تخصصی کردن رفتار با virtual و override
وراثت فقط برای استفاده مجدد از کد نیست؛ بلکه برای تخصصی کردن رفتار نیز به کار میرود. ممکن است
بخواهیم یک متد در کلاس پایه وجود داشته باشد، اما هر کلاس مشتقشده پیادهسازی مخصوص به خود را
برای آن ارائه دهد. برای مثال، همهی حیوانات صدا تولید میکنند، اما صدای هر کدام متفاوت است. برای
این کار از کلمات کلیدی virtual و override استفاده میکنیم.
- virtual: وقتی یک متد را در کلاس پایه با این کلمهی کلیدی مشخص
میکنیم، به کلاسهای مشتقشده اجازه میدهیم که در صورت تمایل، آن را بازنویسی
(override) کنند.
- override: وقتی یک متد را در کلاس مشتقشده با این کلمهی کلیدی
مشخص میکنیم، یعنی در حال ارائهی یک پیادهسازی جدید برای متد virtual کلاس پایه هستیم.
Program.cs
public class Animal
{
public string Name { get; set; }
public virtual void MakeSound()
{
Console.WriteLine("The animal makes a sound.");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Woof woof!");
}
}
Animal genericAnimal = new Animal();
Dog myDog = new Dog();
genericAnimal.MakeSound();
myDog.MakeSound();
در این مثال، متد MakeSound در کلاس Animal به صورت virtual تعریف شده است. کلاس Dog این
متد را override کرده و پیادهسازی مخصوص به خود را ارائه داده است. وقتی myDog.MakeSound() را
فراخوانی میکنیم، نسخهی بازنویسی شده در کلاس Dog اجرا میشود. این مفهوم اساس چندریختی
(Polymorphism) را تشکیل میدهد که در درسهای آینده به آن خواهیم پرداخت.
فراخوانی سازندهی کلاس پایه با base
وقتی یک شیء از کلاس مشتقشده ایجاد میشود، سازندهی آن به طور خودکار یکی از سازندههای کلاس پایه
را فراخوانی میکند تا بخش پایهی شیء نیز مقداردهی اولیه شود. اگر به طور صریح مشخص نکنیم،
سازندهی پیشفرض (بدون پارامتر) کلاس پایه فراخوانی میشود. برای فراخوانی یک سازندهی خاص از کلاس
پایه، از کلمهی کلیدی base استفاده میکنیم.
Program.cs
public class Animal
{
public string Name { get; }
public Animal(string name)
{
this.Name = name;
Console.WriteLine("Animal constructor called.");
}
}
public class Dog : Animal
{
public Dog(string name) : base(name)
{
Console.WriteLine("Dog constructor called.");
}
}
Dog myDog = new Dog("Rex");
Console.WriteLine($"The dog's name is {myDog.Name}.");
خروجی این کد ترتیب فراخوانی سازندهها را نشان میدهد:
Animal constructor called.
Dog constructor called.
The dog's name is Rex.
سینتکس : base(name) به کامپایلر میگوید که قبل از اجرای بدنهی سازندهی Dog، ابتدا سازندهی
کلاس Animal را با آرگومان name فراخوانی کند. این کار تضمین میکند که بخش پایهی شیء به درستی
مقداردهی اولیه شده است.