مقدمه
در درس قبل با مبانی وراثت، نحوهی ایجاد کلاسهای پایه و مشتقشده و بازنویسی متدها با
virtual و
override آشنا شدیم. وراثت مفهومی عمیقتر از اینهاست و C# کلمات کلیدی و
مکانیزمهای
دقیقی برای کنترل کامل این رابطه در اختیار ما قرار میدهد. در این درس، به جزئیات پیشرفتهتری
مانند تأثیر سطوح دسترسی بر وراثت، مخفی کردن اعضای کلاس پایه، جلوگیری از وراثت و معرفی کلاسهای
انتزاعی خواهیم پرداخت. این مفاهیم به شما کمک میکنند تا سلسله مراتبهای نوعی قویتر و با طراحی
بهتری ایجاد کنید.
وراثت و سطوح دسترسی
سطوح دسترسی (Access Modifiers) مشخص میکنند که اعضای یک کلاس پایه تا چه حد برای کلاس مشتقشده
قابل مشاهده و استفاده هستند.
- public: اعضای عمومی کلاس پایه، در کلاس مشتقشده نیز کاملاً قابل
دسترس هستند، گویی عضو خود کلاس مشتقشده بودهاند.
- private: اعضای خصوصی کلاس پایه، تحت هیچ شرایطی
برای کلاس مشتقشده قابل دسترس نیستند. آنها جزئیات پیادهسازی داخلی و محرمانهی کلاس پایه
محسوب میشوند.
اما یک سطح دسترسی دیگر وجود دارد که مشخصاً برای سناریوهای وراثت طراحی شده است: protected.
سطح دسترسی protected
یک عضو protected مانند یک عضو private است، با این تفاوت که
علاوه
بر خود کلاس پایه، برای تمام کلاسهایی که از آن ارثبری میکنند نیز قابل دسترس است. این سطح
دسترسی به شما اجازه میدهد تا جزئیات پیادهسازی را با فرزندان خود به اشتراک بگذارید، بدون اینکه
آنها را برای کل دنیا public کنید.
Program.cs
public class BankAccount
{
private string _owner;
protected decimal _balance;
public BankAccount(string owner)
{
this._owner = owner;
this._balance = 0;
}
}
public class SavingsAccount : BankAccount
{
public SavingsAccount(string owner) : base(owner) { }
public void AddInterest(decimal rate)
{
_balance += _balance * rate;
}
}
در این مثال، کلاس SavingsAccount میتواند مستقیماً فیلد _balance را که به صورت protected
تعریف شده، تغییر دهد، اما به هیچ وجه به فیلد _owner که
private است، دسترسی ندارد.
مخفیسازی اعضای کلاس پایه با new
گاهی اوقات ممکن است بخواهید در کلاس مشتقشده، متدی با نام و امضای دقیقاً یکسان با متدی در کلاس
پایه تعریف کنید، اما متد پایه به صورت virtual تعریف نشده باشد. در این حالت، شما در حال
بازنویسی (overriding) نیستید، بلکه در حال مخفیسازی (hiding) عضو پایه هستید.
برای اینکه به کامپایلر به طور صریح بگویید که این کار عمدی است، از کلمهی کلیدی new (در این کاربرد، نه برای ساخت شیء) استفاده میکنیم.
Program.cs
public class Parent
{
public void ShowInfo() { Console.WriteLine("I am the parent."); }
}
public class Child : Parent
{
public new void ShowInfo() { Console.WriteLine("I am the child."); }
}
Child child = new Child();
child.ShowInfo();
Parent parent = child;
parent.ShowInfo();
نکتهی کلیدی و تفاوت اصلی با override در دو خط آخر مثال مشخص است. وقتی یک عضو را مخفی
میکنید،
متدی که فراخوانی میشود به نوع متغیر بستگی دارد، نه به نوع واقعی شیء. اما در
حالت override، همیشه نسخهی بازنویسی شده در کلاس مشتقشده فراخوانی میشود (مفهوم چندریختی
که
در درس بعد بررسی میشود). به طور کلی، از مخفیسازی اعضا باید با احتیاط استفاده شود و
override
کردن روش ارجح است.
جلوگیری از وراثت با sealed
گاهی اوقات میخواهید یک کلاس را طوری طراحی کنید که هیچ کلاس دیگری نتواند از آن ارثبری کند. این
کار معمولاً برای کلاسهایی انجام میشود که کامل هستند و برای توسعه طراحی نشدهاند، یا به دلایل
امنیتی. برای این منظور از کلمهی کلیدی sealed استفاده میکنیم.
کلاسهای مهر و موم شده (Sealed Classes)
کلاسهای مهر و موم شده با استفاده از کلیدواژه sealed تعریف میشوند.
این کلاسها قابل ارثبری نیستند و هیچ کلاس دیگری نمیتواند از آنها مشتق شود. این ویژگی برای
جلوگیری از تغییر رفتار کلاسهای حساس یا کامل کاربرد دارد.
Program.cs
public sealed class FinalClass
{
}
بسیاری از کلاسهای مهم در فریمورک .NET، مانند System.String و System.Guid، به صورت
sealed تعریف شدهاند تا از تغییر رفتار اصلی آنها جلوگیری شود.
کلاسها و اعضای انتزاعی (Abstract)
در مقابل کلاسهای sealed، کلاسهای انتزاعی (Abstract Classes) قرار
دارند. یک
کلاس انتزاعی، کلاسی ناقص است که نمیتوان مستقیماً از آن نمونهسازی کرد و هدف آن
صرفاً این است که به عنوان یک کلاس پایه برای کلاسهای دیگر عمل کند.
یک کلاس انتزاعی میتواند شامل اعضای انتزاعی (Abstract Members) نیز باشد. یک عضو
انتزاعی (معمولاً متد)، عضوی است که تنها امضای آن تعریف شده و هیچ پیادهسازی ندارد. هر کلاس
غیرانتزاعی که از یک کلاس انتزاعی ارثبری میکند، موظف است تمام اعضای انتزاعی
کلاس پایه را override کرده و برای آنها پیادهسازی فراهم کند.
Program.cs
public abstract class Shape
{
public abstract void Draw();
public void GetInfo() { Console.WriteLine("I am a shape."); }
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle...");
}
}
Circle c = new Circle();
c.Draw();
c.GetInfo();
کلاسهای انتزاعی ابزاری قدرتمند برای تعریف یک "قرارداد" هستند. کلاس Shape میگوید: "هر
چیزی که
یک Shape است، باید قابلیت Draw شدن را داشته باشد، اما من نمیدانم چگونه؛ هر فرزند خودش
باید
بگوید چگونه." این الگو به شما اجازه میدهد تا یک ساختار مشترک برای مجموعهای از کلاسهای مرتبط
تعریف کنید و آنها را مجبور به پیادهسازی رفتارهای ضروری کنید.