مقدمه

در درس قبل با مبانی وراثت، نحوه‌ی ایجاد کلاس‌های پایه و مشتق‌شده و بازنویسی متدها با virtual و override آشنا شدیم. وراثت مفهومی عمیق‌تر از این‌هاست و C# کلمات کلیدی و مکانیزم‌های دقیقی برای کنترل کامل این رابطه در اختیار ما قرار می‌دهد. در این درس، به جزئیات پیشرفته‌تری مانند تأثیر سطوح دسترسی بر وراثت، مخفی کردن اعضای کلاس پایه، جلوگیری از وراثت و معرفی کلاس‌های انتزاعی خواهیم پرداخت. این مفاهیم به شما کمک می‌کنند تا سلسله مراتب‌های نوعی قوی‌تر و با طراحی بهتری ایجاد کنید.

وراثت و سطوح دسترسی

سطوح دسترسی (Access Modifiers) مشخص می‌کنند که اعضای یک کلاس پایه تا چه حد برای کلاس مشتق‌شده قابل مشاهده و استفاده هستند.

  • public: اعضای عمومی کلاس پایه، در کلاس مشتق‌شده نیز کاملاً قابل دسترس هستند، گویی عضو خود کلاس مشتق‌شده بوده‌اند.
  • private: اعضای خصوصی کلاس پایه، تحت هیچ شرایطی برای کلاس مشتق‌شده قابل دسترس نیستند. آن‌ها جزئیات پیاده‌سازی داخلی و محرمانه‌ی کلاس پایه محسوب می‌شوند.

اما یک سطح دسترسی دیگر وجود دارد که مشخصاً برای سناریوهای وراثت طراحی شده است: protected.

سطح دسترسی protected

یک عضو protected مانند یک عضو private است، با این تفاوت که علاوه بر خود کلاس پایه، برای تمام کلاس‌هایی که از آن ارث‌بری می‌کنند نیز قابل دسترس است. این سطح دسترسی به شما اجازه می‌دهد تا جزئیات پیاده‌سازی را با فرزندان خود به اشتراک بگذارید، بدون اینکه آن‌ها را برای کل دنیا public کنید.

Copy Icon Program.cs
public class BankAccount
{
    private string _owner; // Completely private
    protected decimal _balance; // Accessible to derived classes

    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)
    {
        // We can access the protected '_balance' field from the base class.
        _balance += _balance * rate;
        // We CANNOT access the private '_owner' field.
        // Console.WriteLine(_owner); // Compile-time error!
    }
}

در این مثال، کلاس SavingsAccount می‌تواند مستقیماً فیلد _balance را که به صورت protected تعریف شده، تغییر دهد، اما به هیچ وجه به فیلد _owner که private است، دسترسی ندارد.

مخفی‌سازی اعضای کلاس پایه با new

گاهی اوقات ممکن است بخواهید در کلاس مشتق‌شده، متدی با نام و امضای دقیقاً یکسان با متدی در کلاس پایه تعریف کنید، اما متد پایه به صورت virtual تعریف نشده باشد. در این حالت، شما در حال بازنویسی (overriding) نیستید، بلکه در حال مخفی‌سازی (hiding) عضو پایه هستید. برای اینکه به کامپایلر به طور صریح بگویید که این کار عمدی است، از کلمه‌ی کلیدی new (در این کاربرد، نه برای ساخت شیء) استفاده می‌کنیم.

Copy Icon Program.cs
public class Parent
{
    public void ShowInfo() { Console.WriteLine("I am the parent."); }
}

public class Child : Parent
{
    // This 'new' keyword hides the base class's ShowInfo method.
    public new void ShowInfo() { Console.WriteLine("I am the child."); }
}

// --- Usage ---
Child child = new Child();
child.ShowInfo(); // Calls the Child's version. Output: I am the child.

// IMPORTANT: If we treat the child as a parent...
Parent parent = child;
parent.ShowInfo(); // Calls the Parent's version! Output: I am the parent.

نکته‌ی کلیدی و تفاوت اصلی با override در دو خط آخر مثال مشخص است. وقتی یک عضو را مخفی می‌کنید، متدی که فراخوانی می‌شود به نوع متغیر بستگی دارد، نه به نوع واقعی شیء. اما در حالت override، همیشه نسخه‌ی بازنویسی شده در کلاس مشتق‌شده فراخوانی می‌شود (مفهوم چندریختی که در درس بعد بررسی می‌شود). به طور کلی، از مخفی‌سازی اعضا باید با احتیاط استفاده شود و override کردن روش ارجح است.

جلوگیری از وراثت با sealed

گاهی اوقات می‌خواهید یک کلاس را طوری طراحی کنید که هیچ کلاس دیگری نتواند از آن ارث‌بری کند. این کار معمولاً برای کلاس‌هایی انجام می‌شود که کامل هستند و برای توسعه طراحی نشده‌اند، یا به دلایل امنیتی. برای این منظور از کلمه‌ی کلیدی sealed استفاده می‌کنیم.

کلاس‌های مهر و موم شده (Sealed Classes)

کلاس‌های مهر و موم شده با استفاده از کلیدواژه sealed تعریف می‌شوند. این کلاس‌ها قابل ارث‌بری نیستند و هیچ کلاس دیگری نمی‌تواند از آن‌ها مشتق شود. این ویژگی برای جلوگیری از تغییر رفتار کلاس‌های حساس یا کامل کاربرد دارد.

Copy Icon Program.cs
public sealed class FinalClass
{
    // ... class members
}

// The following line would cause a compile-time error.
// public class AnotherClass : FinalClass { } // Error: Cannot derive from sealed type 'FinalClass'.

بسیاری از کلاس‌های مهم در فریم‌ورک .NET، مانند System.String و System.Guid، به صورت sealed تعریف شده‌اند تا از تغییر رفتار اصلی آن‌ها جلوگیری شود.

کلاس‌ها و اعضای انتزاعی (Abstract)

در مقابل کلاس‌های sealed، کلاس‌های انتزاعی (Abstract Classes) قرار دارند. یک کلاس انتزاعی، کلاسی ناقص است که نمی‌توان مستقیماً از آن نمونه‌سازی کرد و هدف آن صرفاً این است که به عنوان یک کلاس پایه برای کلاس‌های دیگر عمل کند.

یک کلاس انتزاعی می‌تواند شامل اعضای انتزاعی (Abstract Members) نیز باشد. یک عضو انتزاعی (معمولاً متد)، عضوی است که تنها امضای آن تعریف شده و هیچ پیاده‌سازی ندارد. هر کلاس غیرانتزاعی که از یک کلاس انتزاعی ارث‌بری می‌کند، موظف است تمام اعضای انتزاعی کلاس پایه را override کرده و برای آن‌ها پیاده‌سازی فراهم کند.

Copy Icon Program.cs
// An abstract base class - cannot be instantiated.
public abstract class Shape
{
    // An abstract method - has no body.
    public abstract void Draw();
    
    // An abstract class can also have regular members.
    public void GetInfo() { Console.WriteLine("I am a shape."); }
}

// A concrete derived class.
public class Circle : Shape
{
    // MUST provide an implementation for the abstract Draw method.
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle...");
    }
}

// --- Usage ---
Circle c = new Circle();
c.Draw();
c.GetInfo();

// The following line would cause a compile-time error.
// Shape s = new Shape(); // Error: Cannot create an instance of the abstract type 'Shape'.

کلاس‌های انتزاعی ابزاری قدرتمند برای تعریف یک "قرارداد" هستند. کلاس Shape می‌گوید: "هر چیزی که یک Shape است، باید قابلیت Draw شدن را داشته باشد، اما من نمی‌دانم چگونه؛ هر فرزند خودش باید بگوید چگونه." این الگو به شما اجازه می‌دهد تا یک ساختار مشترک برای مجموعه‌ای از کلاس‌های مرتبط تعریف کنید و آن‌ها را مجبور به پیاده‌سازی رفتارهای ضروری کنید.