📖 سری آموزشی کتاب C# 12 in a Nutshell
چطور میتونیم برای کلاسهامون یه "قرارداد" تعریف کنیم و بگیم "هر کلاسی که میخواد با من کار کنه، باید این قابلیتها رو داشته باشه"؟
ابزار اصلی ما برای این کار در دنیای شیءگرایی، اینترفیس (Interface) هست. اینترفیسها ستون فقرات معماریهای تمیز، انعطافپذیر و تستپذیر هستن.
اینترفیس فقط رفتار (Behavior) رو تعریف میکنه، نه وضعیت (State). یعنی فقط امضای متدها و پراپرتیها رو داره، نه فیلدهای داده و نه بدنه پیادهسازی برای اعضاش.
تفاوتهای کلیدی با کلاس:
🔹 فقط توابع (متد، پراپرتی، ایونت، ایندکسر) را تعریف میکند، نه فیلدها.
🔹 اعضایش به صورت پیشفرض public و abstract هستند.
🔹 یک کلاس میتواند چندین اینترفیس را پیادهسازی کند (برخلاف وراثت از کلاس که فقط یکیه).
مثال (اینترفیس IEnumerator):
وقتی یه کلاس، اینترفیسی رو پیادهسازی میکنه، قول میده که برای تمام اعضای اون اینترفیس، یک پیادهسازی public ارائه بده.
حالا میتونید یک نمونه از Countdown رو در متغیری از نوع IEnumerator بریزید:
حالا اگه یه کلاس، دو تا اینترفیس رو پیادهسازی کنه که متدهایی با اسم یکسان ولی امضای متفاوت دارن، چی میشه؟ یا اگه بخوایم یه متد از اینترفیس رو از دید عمومی کلاس مخفی کنیم؟
اینجا پای پیادهسازی صریح (Explicit Implementation) به میدون باز میشه. در این حالت، شما اسم اینترفیس رو قبل از اسم متد میارید.
مثال (حل تداخل):
نکته حیاتی: ⚠️ متدی که به صورت صریح پیادهسازی شده، دیگه به صورت عمومی در دسترس نیست. برای صدا زدنش، باید اول آبجکت رو به اون اینترفیس خاص کست کنید:
اینترفیسها، اساس طراحیهای ماژولار، تستپذیر و مبتنی بر اصول SOLID هستن.
📜 قراردادهای کدنویسی در #C: راهنمای کامل اینترفیسها (Interfaces)
چطور میتونیم برای کلاسهامون یه "قرارداد" تعریف کنیم و بگیم "هر کلاسی که میخواد با من کار کنه، باید این قابلیتها رو داشته باشه"؟
ابزار اصلی ما برای این کار در دنیای شیءگرایی، اینترفیس (Interface) هست. اینترفیسها ستون فقرات معماریهای تمیز، انعطافپذیر و تستپذیر هستن.
1️⃣ اینترفیس چیست؟ یک قرارداد، بدون پیادهسازی
اینترفیس فقط رفتار (Behavior) رو تعریف میکنه، نه وضعیت (State). یعنی فقط امضای متدها و پراپرتیها رو داره، نه فیلدهای داده و نه بدنه پیادهسازی برای اعضاش.
تفاوتهای کلیدی با کلاس:
🔹 فقط توابع (متد، پراپرتی، ایونت، ایندکسر) را تعریف میکند، نه فیلدها.
🔹 اعضایش به صورت پیشفرض public و abstract هستند.
🔹 یک کلاس میتواند چندین اینترفیس را پیادهسازی کند (برخلاف وراثت از کلاس که فقط یکیه).
مثال (اینترفیس IEnumerator):
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
2️⃣ پیادهسازی اینترفیس: امضای قرارداد
وقتی یه کلاس، اینترفیسی رو پیادهسازی میکنه، قول میده که برای تمام اعضای اون اینترفیس، یک پیادهسازی public ارائه بده.
internal class Countdown : IEnumerator
{
int count = 11;
public bool MoveNext() => count-- > 0;
public object Current => count;
public void Reset() { throw new NotSupportedException(); }
}
حالا میتونید یک نمونه از Countdown رو در متغیری از نوع IEnumerator بریزید:
IEnumerator e = new Countdown();
while (e.MoveNext())
{
Console.Write(e.Current + " "); // 10 9 8 7 6 5 4 3 2 1 0
}
3️⃣ جادوی پیادهسازی صریح (Explicit Implementation) 🎭
حالا اگه یه کلاس، دو تا اینترفیس رو پیادهسازی کنه که متدهایی با اسم یکسان ولی امضای متفاوت دارن، چی میشه؟ یا اگه بخوایم یه متد از اینترفیس رو از دید عمومی کلاس مخفی کنیم؟
اینجا پای پیادهسازی صریح (Explicit Implementation) به میدون باز میشه. در این حالت، شما اسم اینترفیس رو قبل از اسم متد میارید.
مثال (حل تداخل):
interface I1 { void Foo(); }
interface I2 { int Foo(); }
public class Widget : I1, I2
{
public void Foo() // پیادهسازی پیشفرض برای I1
{
Console.WriteLine("Widget's implementation of I1.Foo");
}
// پیادهسازی صریح برای حل تداخل با I1.Foo
int I2.Foo()
{
Console.WriteLine("Widget's implementation of I2.Foo");
return 42;
}
}نکته حیاتی: ⚠️ متدی که به صورت صریح پیادهسازی شده، دیگه به صورت عمومی در دسترس نیست. برای صدا زدنش، باید اول آبجکت رو به اون اینترفیس خاص کست کنید:
Widget w = new Widget();
w.Foo(); // I1.Foo صدا زده میشه
((I1)w).Foo(); // I1.Foo صدا زده میشه
((I2)w).Foo(); // I2.Foo صدا زده میشه
🤔 حرف حساب و تجربه شما
اینترفیسها، اساس طراحیهای ماژولار، تستپذیر و مبتنی بر اصول SOLID هستن.
🔖 هشتگها:
#CSharp #DotNet #OOP #Interface #CleanCode
📖 سری آموزشی کتاب C# 12 in a Nutshell
تو پست قبلی، وراثت و اینترفیسها رو جداگونه بررسی کردیم. اما وقتی این دو تا با هم ترکیب میشن، دنیایی از نکات ظریف و الگوهای پیشرفته به وجود میاد.
امروز میخوایم یاد بگیریم چطور یه عضو اینترفیس رو در سلسلهمراتب وراثت به درستی override کنیم.
به صورت پیشفرض، وقتی یه عضو اینترفیس رو پیادهسازی میکنید، اون sealed (مهر و موم شده) هست. برای اینکه به کلاسهای فرزند اجازه override کردنش رو بدید، باید اون رو صراحتاً virtual مشخص کنید. این کار به شما اجازه میده از قدرت کامل چندریختی (Polymorphism) استفاده کنید.
حالا فرض کنید کلاس پدر، متد رو virtual نکرده. یه راه برای "override" کردنش، بازپیادهسازی اینترفیس در کلاس فرزنده. این کار، پیادهسازی رو فقط وقتی که آبجکت از طریق خود اینترفیس صدا زده بشه، "هایجک" میکنه.
تلهی بزرگ: ☠️ اگه پیادهسازی در کلاس پدر به صورت ضمنی (public) باشه، این الگو باعث رفتار متناقض و خطرناک میشه!
بازپیادهسازی معمولاً یه راه حل ضعیف و نشانهی طراحی بده. دو الگوی خیلی بهتر برای طراحی کلاسهای توسعهپذیر وجود داره:
الگوی اول: اگه پیادهسازی ضمنیه، همیشه virtual ـش کنید (همون روش شماره ۱).
الگوی دوم (برای پیادهسازی صریح): پیادهسازی صریح (explicit) اینترفیس رو به یک متد protected virtual وصل کنید. این الگو، قدرت کامل رو به کلاسهای فرزند میده تا رفتار رو به صورت امن override کنن.
این الگوها، تفاوت بین یه کتابخونه قابل اعتماد و یه کتابخونه شکننده رو رقم میزنن. طراحی برای توسعهپذیری، نشانه یک معمار نرمافزار حرفهایه.
🧩 وراثت و اینترفیسها در #C: بازپیادهسازی و الگوهای حرفهای
تو پست قبلی، وراثت و اینترفیسها رو جداگونه بررسی کردیم. اما وقتی این دو تا با هم ترکیب میشن، دنیایی از نکات ظریف و الگوهای پیشرفته به وجود میاد.
امروز میخوایم یاد بگیریم چطور یه عضو اینترفیس رو در سلسلهمراتب وراثت به درستی override کنیم.
1️⃣ روش استاندارد: virtual و override
به صورت پیشفرض، وقتی یه عضو اینترفیس رو پیادهسازی میکنید، اون sealed (مهر و موم شده) هست. برای اینکه به کلاسهای فرزند اجازه override کردنش رو بدید، باید اون رو صراحتاً virtual مشخص کنید. این کار به شما اجازه میده از قدرت کامل چندریختی (Polymorphism) استفاده کنید.
public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
public virtual void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox
{
public override void Undo() => Console.WriteLine("RichTextBox.Undo");
}
// --- نتایج ---
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo
((TextBox)r).Undo(); // RichTextBox.Undo (رفتار یکپارچه و درست)2️⃣ تکنیک خطرناک: بازپیادهسازی
(Re-implementation) ⚠️
حالا فرض کنید کلاس پدر، متد رو virtual نکرده. یه راه برای "override" کردنش، بازپیادهسازی اینترفیس در کلاس فرزنده. این کار، پیادهسازی رو فقط وقتی که آبجکت از طریق خود اینترفیس صدا زده بشه، "هایجک" میکنه.
تلهی بزرگ: ☠️ اگه پیادهسازی در کلاس پدر به صورت ضمنی (public) باشه، این الگو باعث رفتار متناقض و خطرناک میشه!
public class TextBox : IUndoable
{
// این متد virtual نیست!
public void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox, IUndoable // بازپیادهسازی اینترفیس
{
// اینجا new هم میتونستیم بذاریم
public void Undo() => Console.WriteLine("RichTextBox.Undo");
}
// --- نتایج متناقض و خطرناک ---
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo (هایجک شد)
((TextBox)r).Undo(); // TextBox.Undo (فاجعه! رفتار چندریختی شکست)
3️⃣ الگوهای حرفهای (جایگزینهای بهتر) ✅
بازپیادهسازی معمولاً یه راه حل ضعیف و نشانهی طراحی بده. دو الگوی خیلی بهتر برای طراحی کلاسهای توسعهپذیر وجود داره:
الگوی اول: اگه پیادهسازی ضمنیه، همیشه virtual ـش کنید (همون روش شماره ۱).
الگوی دوم (برای پیادهسازی صریح): پیادهسازی صریح (explicit) اینترفیس رو به یک متد protected virtual وصل کنید. این الگو، قدرت کامل رو به کلاسهای فرزند میده تا رفتار رو به صورت امن override کنن.
public class TextBox : IUndoable
{
// پیادهسازی صریح، کار را به یک متد مجازی و محافظتشده میسپارد
void IUndoable.Undo() => Undo();
protected virtual void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox
{
protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
}
🤔 حرف حساب و تجربه شما
این الگوها، تفاوت بین یه کتابخونه قابل اعتماد و یه کتابخونه شکننده رو رقم میزنن. طراحی برای توسعهپذیری، نشانه یک معمار نرمافزار حرفهایه.
🔖 هشتگها:
#CSharp #DotNet #OOP #Interface #Inheritance
📖 سری آموزشی کتاب C# 12 in a Nutshell
یکی از اساسیترین سوالات در طراحی شیءگرا اینه: کی باید از یه class و وراثت استفاده کنیم و کی باید بریم سراغ interface؟
این فقط یه انتخاب سینتکسی نیست، یه تصمیم مهم معماریه. بیاید با یه قانون ساده و یه مثال عالی، این موضوع رو برای همیشه روشن کنیم.
به عنوان یک قانون کلی و کاربردی:
از class و وراثت استفاده کن:
وقتی تایپهای شما به صورت طبیعی، پیادهسازی مشترکی (shared implementation) دارند. یعنی کدهای مشترکی بینشون هست که میخواید از تکرارش جلوگیری کنید.
از interface استفاده کن:
وقتی تایپهای شما رفتار مشترکی (shared behavior) دارند، ولی هر کدوم پیادهسازی مستقل (independent implementation) و متفاوتی از اون رفتار رو ارائه میدن.
فرض کنید میخوایم موجودات مختلف رو مدلسازی کنیم.
مشکل (استفاده فقط از کلاس): 👎
یه "عقاب" هم "پرنده" است، هم "موجود پرنده" و هم "گوشتخوار". چون #C وراثت چندگانه از کلاسها رو پشتیبانی نمیکنه، کد زیر کامپایل نمیشه!
راه حل (ترکیب کلاس و اینترفیس): 👍
حالا قانون طلایی رو به کار میبریم. "پرنده" بودن احتمالاً یه سری پیادهسازی و ویژگی مشترک داره، پس
این مثال نشون میده که چطور با انتخاب درست بین کلاس و اینترفیس، میتونید ساختارهای پیچیده و در عین حال تمیز و انعطافپذیری طراحی کنید.
🏛 کلاس در برابر اینترفیس: کدام را و چه زمانی انتخاب کنیم؟
یکی از اساسیترین سوالات در طراحی شیءگرا اینه: کی باید از یه class و وراثت استفاده کنیم و کی باید بریم سراغ interface؟
این فقط یه انتخاب سینتکسی نیست، یه تصمیم مهم معماریه. بیاید با یه قانون ساده و یه مثال عالی، این موضوع رو برای همیشه روشن کنیم.
1️⃣ قانون طلایی: پیادهسازی مشترک در برابر رفتار مشترک ⚖️
به عنوان یک قانون کلی و کاربردی:
از class و وراثت استفاده کن:
وقتی تایپهای شما به صورت طبیعی، پیادهسازی مشترکی (shared implementation) دارند. یعنی کدهای مشترکی بینشون هست که میخواید از تکرارش جلوگیری کنید.
از interface استفاده کن:
وقتی تایپهای شما رفتار مشترکی (shared behavior) دارند، ولی هر کدوم پیادهسازی مستقل (independent implementation) و متفاوتی از اون رفتار رو ارائه میدن.
2️⃣ مثال عملی: دنیای حیوانات 🦅
فرض کنید میخوایم موجودات مختلف رو مدلسازی کنیم.
مشکل (استفاده فقط از کلاس): 👎
یه "عقاب" هم "پرنده" است، هم "موجود پرنده" و هم "گوشتخوار". چون #C وراثت چندگانه از کلاسها رو پشتیبانی نمیکنه، کد زیر کامپایل نمیشه!
abstract class Animal {}
abstract class Bird : Animal {}
abstract class FlyingCreature : Animal {}
abstract class Carnivore : Animal {}
// ❌ خطای کامپایل! یک کلاس نمیتونه از چند کلاس ارثبری کنه
class Eagle : Bird, FlyingCreature, Carnivore {}راه حل (ترکیب کلاس و اینترفیس): 👍
حالا قانون طلایی رو به کار میبریم. "پرنده" بودن احتمالاً یه سری پیادهسازی و ویژگی مشترک داره، پس
Bird یه class باقی میمونه. اما "پرواز کردن" یا "گوشتخوار بودن"، رفتارهایی هستن که موجودات مختلف (مثل حشره و پرنده) به شکلهای کاملاً متفاوتی انجام میدن. پس اینها بهترین کاندیدا برای اینترفیس هستن!// اینترفیسها فقط "رفتار" رو تعریف میکنن
interface IFlyingCreature { }
interface ICarnivore { }
abstract class Animal {}
// کلاسها "پیادهسازی" مشترک رو ارائه میدن
abstract class Bird : Animal {}
// ✅ حالا درسته!
// عقاب از کلاس Bird ارث میبره و دو اینترفیس رو پیادهسازی میکنه
class Eagle : Bird, IFlyingCreature, ICarnivore {}
🤔 حرف حساب و تجربه شما
این مثال نشون میده که چطور با انتخاب درست بین کلاس و اینترفیس، میتونید ساختارهای پیچیده و در عین حال تمیز و انعطافپذیری طراحی کنید.
🔖 هشتگها:
#CSharp #DotNet #OOP #SoftwareArchitecture #Interface #CleanCode
📖 سری آموزشی کتاب C# 12 in a Nutshell
تو پست قبلی، یاد گرفتیم که کی باید از اینترفیس استفاده کنیم. اما داستان اینترفیسها به تعریف چند متد ختم نمیشه! در نسخههای مدرن #C، اینترفیسها به قابلیتهای فوقالعاده قدرتمندی مجهز شدن که قواعد بازی رو عوض میکنن.
امروز با این ابرقدرتها آشنا میشیم.
تصور کنید یه کتابخونه نوشتید و هزاران نفر دارن از اینترفیس ILogger شما استفاده میکنن. حالا اگه بخواید یه متد جدید به این اینترفیس اضافه کنید، کد تمام اون هزار نفر میشکنه!
Default Interface Members
این مشکل رو حل میکنه. شما میتونید یه متد جدید رو با پیادهسازی پیشفرض به اینترفیس اضافه کنید. این یعنی کلاسهایی که از اینترفیس شما استفاده میکنن، مجبور نیستن فوراً این متد جدید رو پیادهسازی کنن.
نکته کلیدی: ⚠️ این پیادهسازیهای پیشفرض، به صورت صریح (explicit) هستن. یعنی اگه کلاسی اون متد رو پیادهسازی نکنه، برای صدا زدنش باید حتماً آبجکت رو به خود اینترفیس کست کنید.
بله، درست خوندید! اینترفیسها در #C مدرن حتی میتونن اعضای استاتیک هم داشته باشن!
اعضای استاتیک غیرمجازی:
این اعضا (مثل فیلدها و متدهای استاتیک) برای تعریف مقادیر ثابت یا متدهای کمکی که به خود اینترفیس مرتبطن، عالین.
این یکی از پیشرفتهترین قابلیتهای #C هست و درهای دنیای جدیدی به اسم "چندریختی استاتیک" (Static Polymorphism) رو باز میکنه (مثلاً برای Generic Math). کلاسهایی که این اینترفیس رو پیادهسازی میکنن، باید اون عضو استاتیک رو هم پیادهسازی کنن.
یه نکته پرفورمنسی مهم: وقتی شما یه struct (که Value Type هست) رو به یک اینترفیسی که پیادهسازی کرده کست میکنید، عمل Boxing اتفاق میفته و اون struct روی هیپ کپی میشه. این کار هزینه داره!
این قابلیتهای مدرن، اینترفیسها رو از یه قرارداد ساده به یه ابزار طراحی فوقالعاده قدرتمند تبدیل کردن.
🚀 انقلاب اینترفیسها در #C مدرن: Default و Static Members
تو پست قبلی، یاد گرفتیم که کی باید از اینترفیس استفاده کنیم. اما داستان اینترفیسها به تعریف چند متد ختم نمیشه! در نسخههای مدرن #C، اینترفیسها به قابلیتهای فوقالعاده قدرتمندی مجهز شدن که قواعد بازی رو عوض میکنن.
امروز با این ابرقدرتها آشنا میشیم.
1️⃣ Default Interface Members (C# 8):
ارتقاء بدون شکستن کد! ✨
تصور کنید یه کتابخونه نوشتید و هزاران نفر دارن از اینترفیس ILogger شما استفاده میکنن. حالا اگه بخواید یه متد جدید به این اینترفیس اضافه کنید، کد تمام اون هزار نفر میشکنه!
Default Interface Members
این مشکل رو حل میکنه. شما میتونید یه متد جدید رو با پیادهسازی پیشفرض به اینترفیس اضافه کنید. این یعنی کلاسهایی که از اینترفیس شما استفاده میکنن، مجبور نیستن فوراً این متد جدید رو پیادهسازی کنن.
نکته کلیدی: ⚠️ این پیادهسازیهای پیشفرض، به صورت صریح (explicit) هستن. یعنی اگه کلاسی اون متد رو پیادهسازی نکنه، برای صدا زدنش باید حتماً آبجکت رو به خود اینترفیس کست کنید.
public interface ILogger
{
// متد جدید با پیادهسازی پیشفرض
void Log(string text) => Console.WriteLine(text);
void LogError(Exception ex); // متد قدیمی بدون پیادهسازی
}
public class MyLogger : ILogger
{
// این کلاس فقط مجبوره LogError رو پیادهسازی کنه
public void LogError(Exception ex) { /* ... */ }
}
// --- نحوه استفاده ---
var logger = new MyLogger();
// logger.Log("hi"); // ❌ خطای زمان کامپایل!
((ILogger)logger).Log("hi"); // ✅ درسته!
2️⃣ Static Interface Members:
ابزارهای کمکی و پلیمورفیسم استاتیک ⚙️
بله، درست خوندید! اینترفیسها در #C مدرن حتی میتونن اعضای استاتیک هم داشته باشن!
اعضای استاتیک غیرمجازی:
این اعضا (مثل فیلدها و متدهای استاتیک) برای تعریف مقادیر ثابت یا متدهای کمکی که به خود اینترفیس مرتبطن، عالین.
public interface ILogger
{
static string DefaultPrefix = "[INFO]: ";
void Log(string text) => Console.WriteLine(DefaultPrefix + text);
}
اعضای استاتیک مجازی/انتزاعی (از 11 #C):
این یکی از پیشرفتهترین قابلیتهای #C هست و درهای دنیای جدیدی به اسم "چندریختی استاتیک" (Static Polymorphism) رو باز میکنه (مثلاً برای Generic Math). کلاسهایی که این اینترفیس رو پیادهسازی میکنن، باید اون عضو استاتیک رو هم پیادهسازی کنن.
3️⃣ نکته فنی: اینترفیسها و Boxing
یه نکته پرفورمنسی مهم: وقتی شما یه struct (که Value Type هست) رو به یک اینترفیسی که پیادهسازی کرده کست میکنید، عمل Boxing اتفاق میفته و اون struct روی هیپ کپی میشه. این کار هزینه داره!
interface I { void Foo(); }
struct S : I { public void Foo() {} }
S s = new S();
s.Foo(); // بدون Boxing
I i = s; // ❌ Boxing اتفاق میفته!
i.Foo();🤔 حرف حساب و تجربه شما
این قابلیتهای مدرن، اینترفیسها رو از یه قرارداد ساده به یه ابزار طراحی فوقالعاده قدرتمند تبدیل کردن.
🔖 هشتگها:
#CSharp #DotNet #OOP #Interface #ModernCSharp