آموزش .NET یکی از بهترین راهها برای ورود به دنیای برنامهنویسی حرفهای است و برنامهنویسی شیءگرا (OOP) در .NET با C# هسته اصلی این فریمورک را تشکیل میدهد. OOP بر اساس چهار اصل اساسی (وراثت، چندریختی، کپسولهسازی و انتزاع) بنا شده است که به شما کمک میکند برنامههایی منسجم و قابلتوسعه طراحی کنید. در ادامه، این اصول و ابزارهای مرتبط در .NET را بررسی میکنیم.
کلاسها و اشیاء در .NET
کلاسها و اشیاء بهعنوان سنگبنای برنامهنویسی شیءگرا (OOP) در .NET با C# شناخته میشوند و درک عمیق آنها برای هر برنامهنویسی که میخواهد با این فریمورک کار کند، ضروری است. در این بخش، بهطور مفصل به مفهوم کلاسها و اشیاء، نقش آنها در برنامهنویسی، نحوه تعریف و استفاده از آنها در C# و همچنین جزئیات بیشتری در مورد ویژگیها و رفتارها میپردازیم.
کلاس چیست؟
کلاس مانند یک نقشه یا الگو (Blueprint) عمل میکند که مشخص میکند یک شیء چه دادههایی (ویژگیها یا Properties) و چه تواناییهایی (رفتارها یا Methods) خواهد داشت. به عبارت سادهتر، کلاس یک ساختار انتزاعی است که تعریف میکند یک موجودیت در دنیای واقعی (مثل ماشین، انسان یا حساب بانکی) چگونه در برنامه شما نمایش داده شود. در برنامهنویسی شیءگرا (OOP) در .NET با C#، کلاسها با کلمه کلیدی class تعریف میشوند.
شیء چیست؟
شیء یک نمونه (Instance) مشخص و قابلاستفاده از یک کلاس است که در حافظه کامپیوتر ایجاد میشود. وقتی یک کلاس تعریف میکنید، هنوز چیزی در برنامه شما وجود ندارد تا زمانی که یک شیء از آن کلاس بسازید. به این فرآیند “ایجاد نمونه” یا “Instantiation” میگویند که با استفاده از کلمه کلیدی new انجام میشود.
اجزای اصلی کلاس
ویژگیها (Properties): متغیرهایی هستند که دادههای مربوط به یک شیء را نگه میدارند. مثلاً رنگ یا سرعت یک ماشین.
متدها (Methods): توابعی هستند که رفتار شیء را تعریف میکنند، مثل حرکت کردن یا توقف یک ماشین.
سازندهها (Constructors): متدهای خاصی که هنگام ساخت یک شیء اجرا میشوند و برای مقداردهی اولیه ویژگیها استفاده میشوند.
مثال ساده
فرض کنید میخواهیم یک کلاس برای “ماشین” بسازیم که نهتنها ویژگیها و رفتارها را نشان دهد، بلکه شامل یک سازنده نیز باشد:
public class Car
{
// ویژگیها (Properties)
public string Color { get; set; }
public int Speed { get; set; }
public bool IsEngineOn { get; private set; } // فقط از داخل کلاس قابل تغییر است
// سازنده (Constructor)
public Car(string color, int speed)
{
Color = color;
Speed = speed;
IsEngineOn = false; // مقدار پیشفرض
}
// متدها (Methods)
public void StartEngine()
{
IsEngineOn = true;
Console.WriteLine("موتور ماشین روشن شد.");
}
public void Drive()
{
if (IsEngineOn)
{
Console.WriteLine($"ماشین با سرعت {Speed} کیلومتر بر ساعت در حال حرکت است!");
}
else
{
Console.WriteLine("لطفاً ابتدا موتور را روشن کنید!");
}
}
public void Stop()
{
Console.WriteLine("ماشین متوقف شد.");
IsEngineOn = false;
}
}
class Program
{
static void Main(string[] args)
{
// ایجاد یک شیء با استفاده از سازنده
Car myCar = new Car("قرمز", 120);
// استفاده از متدها و ویژگیها
Console.WriteLine($"رنگ ماشین: {myCar.Color}");
myCar.StartEngine();
myCar.Drive();
myCar.Stop();
}
}
خروجی:
رنگ ماشین: قرمز موتور ماشین روشن شد. ماشین با سرعت 120 کیلومتر بر ساعت در حال حرکت است! ماشین متوقف شد.
توضیحات مثال
ویژگیها:
Color و Speed بهصورت عمومی (public) تعریف شدهاند تا از بیرون کلاس قابل دسترسی باشند.
IsEngineOn با setter خصوصی (private set) تعریف شده تا فقط از داخل کلاس قابل تغییر باشد، که این یک نمونه ساده از کپسولهسازی است.
سازنده:
سازنده (Constructor) هنگام ایجاد شیء فراخوانی میشود و مقادیر اولیه Color و Speed را تنظیم میکند.
متدها:
StartEngine وضعیت موتور را تغییر میدهد.
Drive یک رفتار شرطی دارد که وابسته به وضعیت موتور است.
Stop ماشین را متوقف کرده و موتور را خاموش میکند.
چرا کلاسها و اشیاء مهماند؟
در برنامهنویسی شیءگرا (OOP) در .NET با C#، کلاسها و اشیاء به شما امکان میدهند:
مدلسازی دنیای واقعی: موجودیتهایی مثل ماشین، انسان یا هر چیز دیگر را بهصورت قابلفهم در برنامه خود پیادهسازی کنید.
استفاده مجدد: یک کلاس را یک بار تعریف کنید و چندین شیء با ویژگیهای مختلف از آن بسازید.
مدیریت پیچیدگی: با سازماندهی کد در قالب کلاسها، برنامه شما خواناتر و قابلنگهداری میشود.
ایجاد چندین شیء
یکی از قدرتهای کلاسها این است که میتوانید چندین نمونه از آنها بسازید:
Car car1 = new Car("آبی", 100);
Car car2 = new Car("مشکی", 150);
car1.Drive(); // ماشین آبی حرکت نمیکند چون موتورش خاموش است
car2.StartEngine();
car2.Drive(); // ماشین مشکی با سرعت 150 حرکت میکند
نکته تکمیلی: مقایسه با دنیای واقعی
تصور کنید کلاس Car مثل یک طرح کارخانه است که مشخص میکند ماشینها چه امکاناتی دارند. هر ماشین واقعی که از خط تولید بیرون میآید (مثل myCar) یک شیء است که بر اساس آن طرح ساخته شده و میتواند رنگ، سرعت یا وضعیت متفاوتی داشته باشد.
با تسلط بر کلاسها و اشیاء، پایه محکمی برای یادگیری سایر مفاهیم برنامهنویسی شیءگرا (OOP) در .NET با C# خواهید داشت، مثل وراثت یا چندریختی که در بخشهای بعدی بررسی میکنیم.
وراثت (Inheritance) در .NET
وراثت (Inheritance) یکی از اصول کلیدی برنامهنویسی شیءگرا (OOP) در .NET با C# است که به شما امکان میدهد یک کلاس جدید (که به آن کلاس فرزند یا Derived Class میگویند) را بر اساس یک کلاس موجود (که به آن کلاس پدر یا Base Class میگویند) ایجاد کنید. این مکانیزم قدرتمند نهتنها باعث کاهش تکرار کد میشود، بلکه استفاده مجدد از کدها را آسانتر کرده و ساختار برنامه را منظمتر میکند. در این بخش، بهطور جامع به مفهوم وراثت، نحوه کار آن در .NET، مزایا، محدودیتها و مثالهای عملی خواهیم پرداخت.
وراثت چیست؟
وراثت به معنای انتقال ویژگیها و رفتارها از یک کلاس پدر به یک کلاس فرزند است. به بیان ساده، وقتی یک کلاس از کلاس دیگری ارث میبرد، تمام اعضای عمومی و محافظتشده (public و protected) کلاس پدر به کلاس فرزند منتقل میشود. این قابلیت در برنامهنویسی شیءگرا (OOP) در .NET با C# به شما اجازه میدهد سلسلهمراتبی از کلاسها ایجاد کنید که با یکدیگر مرتبط هستند.
نحوه تعریف وراثت در C#
در C#، وراثت با استفاده از علامت : بین نام کلاس فرزند و کلاس پدر مشخص میشود. بهعنوان مثال، اگر بخواهیم کلاسی به نام Car از کلاسی به نام Vehicle ارث ببرد، این کار بهصورت زیر انجام میشود:
public class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("موتور روشن شد.");
}
}
public class Car : Vehicle // وراثت
{
public int Doors { get; set; }
}
چگونه کار میکند؟
در مثال بالا:
Vehicle کلاس پدر است که ویژگی Brand و متد StartEngine را دارد.
Car کلاس فرزند است که از Vehicle ارث میبرد و علاوه بر دسترسی به Brand و StartEngine، ویژگی خاص خودش یعنی Doors را نیز اضافه میکند.
مثال عملی
بیایید یک سناریوی واقعیتر را بررسی کنیم که شامل سازندهها و متدهای اضافی باشد:
public class Vehicle
{
public string Brand { get; set; }
public int Year { get; set; }
// سازنده کلاس پدر
public Vehicle(string brand, int year)
{
Brand = brand;
Year = year;
}
public void StartEngine()
{
Console.WriteLine("موتور روشن شد.");
}
public void DisplayInfo()
{
Console.WriteLine($"برند: {Brand}، سال تولید: {Year}");
}
}
public class Car : Vehicle
{
public int Doors { get; set; }
public bool IsConvertible { get; set; }
// سازنده کلاس فرزند که سازنده پدر را فراخوانی میکند
public Car(string brand, int year, int doors, bool isConvertible)
: base(brand, year) // فراخوانی سازنده کلاس پدر
{
Doors = doors;
IsConvertible = isConvertible;
}
public void OpenRoof()
{
if (IsConvertible)
Console.WriteLine("سقف ماشین باز شد.");
else
Console.WriteLine("این ماشین سقف بازشو ندارد!");
}
}
class Program
{
static void Main(string[] args)
{
Car myCar = new Car("BMW", 2023, 4, true);
myCar.DisplayInfo(); // از کلاس پدر
myCar.StartEngine(); // از کلاس پدر
Console.WriteLine($"تعداد درها: {myCar.Doors}"); // از کلاس فرزند
myCar.OpenRoof(); // از کلاس فرزند
}
}
خروجی:
برند: BMW، سال تولید: 2023 موتور روشن شد. تعداد درها: 4 سقف ماشین باز شد.
توضیحات مثال
سازندهها در وراثت:
کلاس پدر (Vehicle) یک سازنده دارد که Brand و Year را مقداردهی میکند.
کلاس فرزند (Car) با استفاده از base سازنده پدر را فراخوانی میکند تا از تکرار کد جلوگیری شود.
ویژگیها و متدها:
Car به تمام اعضای عمومی Vehicle (مثل Brand और StartEngine) دسترسی دارد و ویژگیهای خاص خودش (Doors و IsConvertible) را نیز اضافه کرده است.
رفتار خاص:
متد OpenRoof فقط در Car وجود دارد و به نوع ماشین (تبدیلپذیر یا معمولی) بستگی دارد.
مزایای وراثت
کاهش تکرار کد: بهجای نوشتن ویژگیها یا متدهای مشترک در هر کلاس، آنها را یک بار در کلاس پدر تعریف میکنید.
استفاده مجدد: کلاسهای فرزند میتوانند از کدهای موجود در کلاس پدر استفاده کنند.
سلسلهمراتب منطقی: وراثت به شما اجازه میدهد روابط دنیای واقعی (مثل اینکه ماشین نوعی وسیله نقلیه است) را مدلسازی کنید.
انعطافپذیری: میتوانید رفتارهای کلاس پدر را در کلاس فرزند گسترش دهید یا تغییر دهید (مثلاً با بازنویسی متدها که در بخش چندریختی بررسی میشود).
محدودیتها و نکات مهم
وراثت تکگانه در C#: در برنامهنویسی شیءگرا (OOP) در .NET با C#، یک کلاس فقط میتواند از یک کلاس دیگر ارث ببرد (برخلاف برخی زبانها مثل C++ که وراثت چندگانه را پشتیبانی میکنند). برای جبران این محدودیت، از 인터فیسها استفاده میشود (که بعداً توضیح داده خواهد شد).
دسترسی به اعضا: فقط اعضای public و protected از کلاس پدر به فرزند منتقل میشوند. اعضای private قابل ارثبری نیستند.
کلاسهای مهر و موم شده (Sealed): اگر کلاسی با کلمه کلیدی sealed تعریف شود، نمیتوان از آن ارث برد:
public sealed class Vehicle { }
// خطا: نمیتوان از Vehicle ارث برد
مثال پیشرفتهتر: سلسلهمراتب عمیقتر
public class Vehicle
{
public string Brand { get; set; }
public void StartEngine() { Console.WriteLine("موتور روشن شد."); }
}
public class Car : Vehicle
{
public int Doors { get; set; }
}
public class SportsCar : Car
{
public int TopSpeed { get; set; }
public void Boost()
{
Console.WriteLine($"سرعت به {TopSpeed} رسید!");
}
}
class Program
{
static void Main(string[] args)
{
SportsCar mySportsCar = new SportsCar();
mySportsCar.Brand = "Ferrari"; // از Vehicle
mySportsCar.Doors = 2; // از Car
mySportsCar.TopSpeed = 300; // از SportsCar
mySportsCar.StartEngine(); // از Vehicle
mySportsCar.Boost(); // از SportsCar
}
}
در اینجا، SportsCar از Car ارث میبرد و Car از Vehicle. این سلسلهمراتب نشاندهنده قدرت وراثت در سازماندهی کدهاست.
چندریختی (Polymorphism) در .NET
چندریختی (Polymorphism) یکی از اصول اساسی برنامهنویسی شیءگرا (OOP) در .NET با C# است که به معنای “چندشکل بودن” است. این مفهوم به شما اجازه میدهد یک عملیات یا متد واحد را به شیوههای مختلف اجرا کنید، بسته به اینکه کدام کلاس آن را پیادهسازی کرده باشد. در واقع، چندریختی این امکان را فراهم میکند که اشیاء مختلف با یک رابط مشترک (مانند متد یا رفتار) به شیوهای منحصربهفرد عمل کنند. در C#، این ویژگی معمولاً از طریق متدهای مجازی (virtual) و بازنویسی (override) یا استفاده از اینترفیسها پیادهسازی میشود. در این بخش، این مفهوم را با جزئیات بیشتر، انواع آن، و مثالهای عملی توضیح میدهیم.
چندریختی چیست؟
کلمه “Polymorphism” از ریشه یونانی به معنای “چند شکل” گرفته شده است. در برنامهنویسی، چندریختی به شما اجازه میدهد یک رابط یا متد را در کلاس پدر تعریف کنید و سپس در کلاسهای فرزند آن را به شکلی متفاوت پیادهسازی کنید. این قابلیت نهتنها انعطافپذیری را افزایش میدهد، بلکه کد شما را قابلگسترش و قابلاستفاده مجدد میکند.
انواع چندریختی در .NET
چندریختی در زمان کامپایل (Compile-Time Polymorphism):
این نوع چندریختی با استفاده از “سربارگذاری متدها” (Method Overloading) پیادهسازی میشود، یعنی تعریف چند متد با نام یکسان اما پارامترهای متفاوت.
مثال:
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
}
class Program
{
static void Main(string[] args)
{
Calculator calc = new Calculator();
Console.WriteLine(calc.Add(2, 3)); // خروجی: 5
Console.WriteLine(calc.Add(2.5, 3.7)); // خروجی: 6.2
}
}
در اینجا، متد Add دو شکل مختلف دارد و کامپایلر در زمان کامپایل تصمیم میگیرد کدام نسخه را فراخوانی کند.
چندریختی در زمان اجرا (Run-Time Polymorphism):
این نوع چندریختی با استفاده از متدهای مجازی (virtual) و بازنویسی (override) پیادهسازی میشود و در زمان اجرای برنامه مشخص میشود که کدام متد فراخوانی خواهد شد.
مثال ساده:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("حیوان صدایی تولید میکند.");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("سگ میگوید: واق واق!");
}
}
class Program
{
static void Main(string[] args)
{
Animal myAnimal = new Dog();
myAnimal.Speak(); // خروجی: سگ میگوید: واق واق!
}
}
در اینجا، نوع واقعی شیء (Dog) در زمان اجرا تعیین میکند که کدام نسخه از Speak اجرا شود.
مثال پیشرفتهتر
بیایید یک سناریوی پیچیدهتر را بررسی کنیم که شامل چندین کلاس فرزند باشد:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("حیوان صدایی تولید میکند.");
}
public virtual void Move()
{
Console.WriteLine("حیوان حرکت میکند.");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("سگ میگوید: واق واق!");
}
public override void Move()
{
Console.WriteLine("سگ میدود.");
}
}
public class Bird : Animal
{
public override void Speak()
{
Console.WriteLine("پرنده میگوید: جیک جیک!");
}
public override void Move()
{
Console.WriteLine("پرنده پرواز میکند.");
}
}
class Program
{
static void Main(string[] args)
{
Animal[] animals = new Animal[2];
animals[0] = new Dog();
animals[1] = new Bird();
foreach (Animal animal in animals)
{
animal.Speak();
animal.Move();
Console.WriteLine("-----");
}
}
}
خروجی:
سگ میگوید: واق واق! سگ میدود. ----- پرنده میگوید: جیک جیک! پرنده پرواز میکند. -----
توضیحات مثال
کلاس Animal دو متد مجازی (Speak و Move) دارد.
کلاسهای Dog و Bird این متدها را بازنویسی کرده و رفتار خاص خود را پیادهسازی میکنند.
در آرایه animals، اشیاء با نوع پایه Animal تعریف شدهاند، اما در زمان اجرا، متدهای مربوط به نوع واقعی شیء (یعنی Dog یا Bird) فراخوانی میشوند.
مزایای چندریختی
انعطافپذیری: میتوانید با یک رابط مشترک با اشیاء مختلف کار کنید بدون اینکه نیاز باشد نوع دقیق آنها را بدانید.
گسترشپذیری: افزودن کلاسهای جدید (مثل Cat) بدون تغییر کد موجود امکانپذیر است.
کاهش پیچیدگی: کد شما سادهتر و قابلفهمتر میشود.
نکته: تفاوت Virtual و Override
virtual: در کلاس پدر استفاده میشود تا نشان دهد این متد میتواند در کلاس فرزند بازنویسی شود.
override: در کلاس فرزند استفاده میشود تا نسخه جدیدی از متد مجازی را تعریف کند.
در برنامهنویسی شیءگرا (OOP) در .NET با C#، چندریختی یکی از ابزارهای اصلی برای طراحی سیستمهای پویا و مقیاسپذیر است.
کپسولهسازی (Encapsulation) در .NET
کپسولهسازی (Encapsulation) یکی دیگر از اصول کلیدی برنامهنویسی شیءگرا (OOP) در .NET با C# است که به معنای مخفی کردن جزئیات داخلی یک کلاس و ارائه دسترسی کنترلشده به دادهها و عملیات آن است. این مفهوم به شما کمک میکند تا دادههای حساس را از دستکاری غیرمجاز محافظت کنید و ساختار داخلی کلاس را از دید کاربران یا کدهای خارجی پنهان نگه دارید. در C#، کپسولهسازی با استفاده از مشخصکنندههای دسترسی (Access Modifiers) مثل private، public، protected و ویژگیها (Properties) پیادهسازی میشود.
کپسولهسازی چیست؟
کپسولهسازی را میتوان بهعنوان “بستهبندی” دادهها و متدها در یک واحد (کلاس) تصور کرد، بهطوری که فقط بخشهایی از آن که لازم است، در دسترس دنیای بیرون قرار گیرند. این کار امنیت و پایداری کد را افزایش میدهد، زیرا از تغییرات ناخواسته در دادهها جلوگیری میکند.
ابزارهای کپسولهسازی در C#
مشخصکنندههای دسترسی:
public: عضو از همهجا قابل دسترسی است.
private: عضو فقط از داخل کلاس قابل دسترسی است.
protected: عضو در کلاس و کلاسهای فرزند قابل دسترسی است.
internal: عضو فقط در اسمبلی فعلی قابل دسترسی است.
ویژگیها (Properties): بهجای دسترسی مستقیم به متغیرها، از getter و setter استفاده میشود تا کنترل بیشتری اعمال شود.
مثال ساده
public class BankAccount
{
private decimal balance; // مخفی کردن داده
public void Deposit(decimal amount)
{
if (amount > 0)
balance += amount;
}
public decimal GetBalance()
{
return balance;
}
}
class Program
{
static void Main(string[] args)
{
BankAccount account = new BankAccount();
account.Deposit(1000);
Console.WriteLine(account.GetBalance()); // خروجی: 1000
}
}
در اینجا، balance خصوصی است و فقط از طریق متدهای عمومی (Deposit و GetBalance) قابل مدیریت است.
مثال پیشرفتهتر با ویژگیها
بیایید یک نمونه پیچیدهتر با استفاده از ویژگیها و منطق اضافی بسازیم:
public class BankAccount
{
private decimal balance; // متغیر خصوصی
private string accountNumber; // شماره حساب
// ویژگی با کنترل دسترسی
public decimal Balance
{
get { return balance; }
private set { balance = value; } // فقط داخل کلاس قابل تغییر است
}
public string AccountNumber
{
get { return accountNumber; }
set
{
if (!string.IsNullOrEmpty(value) && value.Length == 10)
accountNumber = value;
else
throw new ArgumentException("شماره حساب باید 10 رقمی باشد.");
}
}
public BankAccount(string accNumber)
{
AccountNumber = accNumber; // استفاده از setter ویژگی
balance = 0;
}
public void Deposit(decimal amount)
{
if (amount > 0)
{
balance += amount;
Console.WriteLine($"{amount} به حساب واریز شد.");
}
else
{
throw new ArgumentException("مبلغ باید مثبت باشد.");
}
}
public void Withdraw(decimal amount)
{
if (amount > 0 && amount <= balance)
{
balance -= amount;
Console.WriteLine($"{amount} از حساب برداشت شد.");
}
else
{
throw new ArgumentException("مبلغ برداشت نامعتبر است.");
}
}
}
class Program
{
static void Main(string[] args)
{
BankAccount account = new BankAccount("1234567890");
Console.WriteLine($"شماره حساب: {account.AccountNumber}");
account.Deposit(1500);
Console.WriteLine($"موجودی: {account.Balance}");
account.Withdraw(500);
Console.WriteLine($"موجودی جدید: {account.Balance}");
}
}
خروجی:
شماره حساب: 1234567890
1500 به حساب واریز شد.
موجودی: 1500
500 از حساب برداشت شد.
موجودی جدید: 1000
توضیحات مثال
متغیرهای خصوصی:
balance و accountNumber خصوصی هستند و مستقیماً از خارج کلاس قابل دسترسی نیستند.
ویژگیها (Properties):
Balance فقط getter عمومی دارد، یعنی فقط قابل خواندن است و setter آن خصوصی است.
AccountNumber دارای setter عمومی است، اما با منطقی که طول شماره حساب را بررسی میکند.
متدها:
Deposit و Withdraw دسترسی به balance را کنترل میکنند و از تغییرات غیرمجاز جلوگیری میکنند.
مزایای کپسولهسازی
امنیت دادهها: با محدود کردن دسترسی مستقیم، از تغییرات ناخواسته یا نادرست جلوگیری میشود.
انعطافپذیری: میتوانید منطق داخلی کلاس را بدون تغییر رابط عمومی آن تغییر دهید.
نگهداری آسان: تغییرات در پیادهسازی داخلی کلاس روی کدهای خارجی اثر نمیگذارد.
نکته: تفاوت با انتزاع
کپسولهسازی گاهی با انتزاع (Abstraction) اشتباه گرفته میشود. کپسولهسازی درباره مخفی کردن جزئیات پیادهسازی است، در حالی که انتزاع درباره نمایش فقط عملکردهای ضروری به کاربر است.
در برنامهنویسی شیءگرا (OOP) در .NET با C#، کپسولهسازی پایهای برای طراحی کلاسهای امن و قابلاعتماد است که به شما کمک میکند برنامههایی پایدار و حرفهای بسازید.
انتزاع (Abstraction) در .NET
انتزاع (Abstraction) یکی از اصول بنیادی برنامهنویسی شیءگرا (OOP) در .NET با C# است که به معنای سادهسازی مسائل پیچیده با نمایش فقط جزئیات ضروری و پنهان کردن پیچیدگیهای غیرضروری است. هدف انتزاع این است که کاربران یا توسعهدهندگان فقط با جنبههای مهم یک موجودیت کار کنند، بدون اینکه درگیر جزئیات داخلی آن شوند. در C#، این مفهوم عمدتاً از طریق کلاسهای انتزاعی (Abstract Classes) و اینترفیسها (Interfaces) پیادهسازی میشود. در این بخش، این موضوع را با جزئیات بیشتر، مثالهای عملی و نکات تکمیلی توضیح میدهیم.
انتزاع چیست؟
انتزاع به شما اجازه میدهد یک مفهوم کلی (مثل “شکل” یا “وسیله نقلیه”) را تعریف کنید و جزئیات خاص (مثل اینکه چگونه مساحت یک شکل محاسبه شود) را به کلاسهای مشتقشده واگذار کنید. این کار باعث میشود کد شما هم سادهتر باشد و هم انعطافپذیرتر، زیرا میتوانید بدون تغییر ساختار کلی، پیادهسازیهای مختلفی اضافه کنید.
ابزارهای انتزاع در .NET
کلاسهای انتزاعی (Abstract Classes):
کلاسهایی هستند که با کلمه کلیدی abstract تعریف میشوند و نمیتوان از آنها مستقیماً شیء ساخت.
میتوانند شامل متدهای انتزاعی (بدون پیادهسازی) و متدهای معمولی (با پیادهسازی) باشند.
برای تعریف یک رفتار مشترک بین کلاسهای مرتبط استفاده میشوند.
متدهای انتزاعی:
متدهایی هستند که با کلمه کلیدی abstract تعریف میشوند و هیچ بدنهای (پیادهسازی) ندارند. کلاسهای فرزند موظفاند آنها را پیادهسازی کنند.
مثال ساده
public abstract class Shape
{
public abstract double CalculateArea(); // متد انتزاعی
}
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Radius = radius;
}
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
class Program
{
static void Main(string[] args)
{
Circle circle = new Circle(5);
Console.WriteLine(circle.CalculateArea()); // خروجی: 78.54
}
}
در اینجا، Shape یک کلاس انتزاعی است که متد CalculateArea را بهصورت انتزاعی تعریف کرده و Circle آن را پیادهسازی میکند.
مثال پیشرفته
یک سناریوی پیچیدهتر با ویژگیها و متدهای اضافی بررسی کنیم:
public abstract class Shape
{
public string Color { get; set; } // ویژگی مشترک
public Shape(string color)
{
Color = color;
}
public abstract double CalculateArea(); // متد انتزاعی
public void DisplayColor() // متد معمولی
{
Console.WriteLine($"رنگ شکل: {Color}");
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(string color, double radius) : base(color)
{
Radius = radius;
}
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(string color, double width, double height) : base(color)
{
Width = width;
Height = height;
}
public override double CalculateArea()
{
return Width * Height;
}
}
class Program
{
static void Main(string[] args)
{
Shape circle = new Circle("قرمز", 5);
Shape rectangle = new Rectangle("آبی", 4, 6);
circle.DisplayColor();
Console.WriteLine($"مساحت دایره: {circle.CalculateArea()}");
rectangle.DisplayColor();
Console.WriteLine($"مساحت مستطیل: {rectangle.CalculateArea()}");
}
}
خروجی:
رنگ شکل: قرمز
مساحت دایره: 78.53981633974483
رنگ شکل: آبی
مساحت مستطیل: 24
توضیحات مثال
کلاس انتزاعی Shape:
ویژگی Color و متد DisplayColor را بهعنوان رفتار مشترک همه شکلها تعریف میکند.
متد CalculateArea انتزاعی است و باید در کلاسهای فرزند پیادهسازی شود.
کلاسهای فرزند:
Circle و Rectangle هر کدام روش خاص خود را برای محاسبه مساحت دارند.
هر دو از سازنده پایه (base) برای مقداردهی Color استفاده میکنند.
چندریختی:
متغیرهای نوع Shape میتوانند به اشیاء Circle یا Rectangle اشاره کنند و متد مناسب در زمان اجرا فراخوانی میشود.
مزایای انتزاع
سادگی: کاربران فقط با رابط کلی (مثل CalculateArea) کار میکنند و نیازی به دانستن جزئیات پیادهسازی ندارند.
انعطافپذیری: میتوانید شکلهای جدیدی (مثل مثلث) اضافه کنید بدون اینکه کد موجود را تغییر دهید.
کنترل: کلاس انتزاعی میتواند رفتارهای پیشفرض را ارائه دهد و رفتارهای خاص را به کلاسهای فرزند واگذار کند.
محدودیتها
نمیتوان از کلاسهای انتزاعی مستقیماً شیء ساخت (مثلاً new Shape() خطا میدهد).
اگر کلاسی حتی یک متد انتزاعی داشته باشد، باید خود کلاس هم abstract باشد.
در برنامهنویسی شیءگرا (OOP) در .NET با C#، انتزاع به شما کمک میکند تا مفاهیم پیچیده را به بخشهای قابلمدیریت تقسیم کنید و کدی تمیز و قابلگسترش بنویسید.
کار با اینترفیسها و کلاسهای انتزاعی در .NET
در برنامهنویسی شیءگرا (OOP) در .NET با C#، اینترفیسها (Interfaces) و کلاسهای انتزاعی (Abstract Classes) ابزارهایی حیاتی برای پیادهسازی انتزاع و تضمین رفتار مشترک بین کلاسهای مختلف هستند. در حالی که کلاسهای انتزاعی ترکیبی از متدهای پیادهسازیشده و انتزاعی را ارائه میدهند، اینترفیسها صرفاً بهعنوان قراردادهایی عمل میکنند که کلاسها باید آنها را پیادهسازی کنند. در این بخش، این دو مفهوم را با جزئیات بیشتر، تفاوتهایشان و مثالهای عملی بررسی میکنیم.
اینترفیس چیست؟
اینترفیس یک ساختار کاملاً انتزاعی است که با کلمه کلیدی interface تعریف میشود و شامل تعریف متدها، ویژگیها یا رویدادها (بدون پیادهسازی) است. کلاسهایی که یک اینترفیس را پیادهسازی میکنند، موظفاند تمام اعضای آن را بهطور کامل پیادهسازی کنند.
مثال ساده
public interface IPlayable
{
void Play();
}
public class Guitar : IPlayable
{
public void Play()
{
Console.WriteLine("گیتار نواخته شد.");
}
}
public class Piano : IPlayable
{
public void Play()
{
Console.WriteLine("پیانو نواخته شد.");
}
}
class Program
{
static void Main(string[] args)
{
IPlayable guitar = new Guitar();
IPlayable piano = new Piano();
guitar.Play();
piano.Play();
}
}
در اینجا، IPlayable تضمین میکند که هر کلاس متد Play را داشته باشد.
مثال پیشرفتهتر با ویژگیها و چند اینترفیس
بیایید یک سناریوی پیچیدهتر را بررسی کنیم که شامل چند اینترفیس و ترکیب آنها باشد:
public interface IPlayable
{
void Play();
string Name { get; } // ویژگی
}
public interface IRecordable
{
void Record();
}
public class Guitar : IPlayable, IRecordable // پیادهسازی چند اینترفیس
{
public string Name => "گیتار";
public void Play()
{
Console.WriteLine("گیتار نواخته شد.");
}
public void Record()
{
Console.WriteLine("صدای گیتار ضبط شد.");
}
}
public class Piano : IPlayable
{
public string Name => "پیانو";
public void Play()
{
Console.WriteLine("پیانو نواخته شد.");
}
}
class Program
{
static void Main(string[] args)
{
IPlayable[] instruments = new IPlayable[] { new Guitar(), new Piano() };
foreach (var instrument in instruments)
{
Console.WriteLine($"نام ساز: {instrument.Name}");
instrument.Play();
}
// بررسی خاص برای IRecordable
Guitar guitar = new Guitar();
if (guitar is IRecordable recordable)
{
recordable.Record();
}
}
}
خروجی:
نام ساز: گیتار گیتار نواخته شد. نام ساز: پیانو پیانو نواخته شد. صدای گیتار ضبط شد.
توضیحات مثال
اینترفیس IPlayable:
شامل یک متد (Play) و یک ویژگی (Name) است که همه سازها باید آنها را داشته باشند.
اینترفیس IRecordable:
متد Record را تعریف میکند که فقط برخی سازها (مثل Guitar) آن را پشتیبانی میکنند.
پیادهسازی چندگانه:
Guitar هر دو اینترفیس را پیادهسازی میکند، اما Piano فقط IPlayable را دارد.
چندریختی:
آرایه از نوع IPlayable میتواند اشیاء مختلفی را نگه دارد و متد مناسب را فراخوانی کند.
مثال ترکیبی:
public abstract class Instrument
{
public string Brand { get; set; }
public abstract void Tune(); // متد انتزاعی
}
public interface IPlayable
{
void Play();
}
public class Guitar : Instrument, IPlayable
{
public Guitar(string brand)
{
Brand = brand;
}
public override void Tune()
{
Console.WriteLine("گیتار کوک شد.");
}
public void Play()
{
Console.WriteLine("گیتار نواخته شد.");
}
}
class Program
{
static void Main(string[] args)
{
Guitar guitar = new Guitar("Fender");
guitar.Tune();
guitar.Play();
Console.WriteLine($"برند: {guitar.Brand}");
}
}
خروجی:
گیتار کوک شد.
گیتار نواخته شد.
برند: Fender
مزایای استفاده از اینترفیسها و کلاسهای انتزاعی
استانداردسازی: رفتار مشترک بین کلاسها تضمین میشود.
انعطافپذیری: میتوانید بدون تغییر کد موجود، کلاسهای جدیدی اضافه کنید.
جداسازی: منطق برنامه به بخشهای مجزا تقسیم میشود.
در برنامهنویسی شیءگرا (OOP) در .NET با C#، ترکیب کلاسهای انتزاعی و اینترفیسها به شما امکان میدهد سیستمی منسجم، مقیاسپذیر و قابلنگهداری طراحی کنید.
نتیجهگیری
برنامهنویسی شیءگرا (OOP) در .NET با C# رویکردی قدرتمند و ساختاریافته برای توسعه نرمافزار است که با تکیه بر مفاهیمی مانند کلاسها و اشیاء، وراثت، چندریختی، کپسولهسازی، انتزاع و کار با اینترفیسها، به برنامهنویسان امکان میدهد کدهایی منسجم، قابلاستفاده مجدد و آسان برای نگهداری بنویسند. این اصول به شما کمک میکنند تا پیچیدگیهای پروژههای بزرگ را مدیریت کرده و سیستمی طراحی کنید که هم انعطافپذیر باشد و هم بهراحتی قابلگسترش. از سطح مبتدی با مفاهیم ساده مانند تعریف کلاسها شروع کنید و بهتدریج به مباحث پیشرفتهتر مثل استفاده از کلاسهای انتزاعی و اینترفیسها برسید. با تسلط بر برنامهنویسی شیءگرا (OOP) در .NET با C#، نهتنها مهارتهای برنامهنویسی خود را ارتقا میدهید، بلکه توانایی ساخت برنامههای حرفهای و کارآمد را نیز به دست خواهید آورد. این مسیر یادگیری، شروعی برای تبدیل شدن به یک توسعهدهنده موفق در اکوسیستم .NET است.
