الوراثة والواجهات في C#: abstract و interface
في درس الكلاسات ورثت كلاساً من آخر باستخدام virtual و override. لكن تصميم البرامج والألعاب الحقيقية يحتاج أكثر: كلاس أب لا يُنشأ منه كائن أصلاً، وقدرات مشتركة بين كلاسات لا قرابة بينها. في هذا الدرس ستتعلم الكلاسات المجردة والواجهات، وهما أساس تصميم أي مشروع C# كبير.
الكلاس المجرد abstract
Section titled “الكلاس المجرد abstract”ما مساحة «شكل»؟ لا إجابة حتى نعرف هل هو دائرة أم مستطيل. الكلاس Shape فكرة عامة، ولا معنى لإنشاء كائن منه:
abstract class Shape{ public string Name { get; }
protected Shape(string name) { Name = name; }
public abstract double Area();
public void Print() { Console.WriteLine($"{Name}: {Area()}"); }}
class Rectangle : Shape{ private readonly double width, height;
public Rectangle(double width, double height) : base("مستطيل") { this.width = width; this.height = height; }
public override double Area() => width * height;}
class Circle : Shape{ private readonly double radius;
public Circle(double radius) : base("دائرة") { this.radius = radius; }
public override double Area() => 3.14 * radius * radius;}Shape[] shapes = { new Rectangle(4, 5), new Circle(10) };foreach (Shape shape in shapes){ shape.Print();}مستطيل: 20دائرة: 314abstract class: كلاس لا يمكن إنشاء كائن منه. السطرnew Shape("شكل")خطأ ترجمة.abstract double Area();: دالة بلا جسم، وكل كلاس وارث ملزم بكتابتها بـoverride.- الكلاس المجرد يحمل مع ذلك خصائص ودوالاً عادية مثل
Print. Printمكتوبة مرة واحدة في الأب، وتستدعي نسخةAreaالخاصة بكل شكل. هذا هو تعدد الأشكال (Polymorphism).
الكلاس المجرد مثل قالب كعك بلا حشوة: الشكل العام جاهز، لكن لا تُقدَّم الكعكة حتى يختار كل طاهٍ حشوته.
الواجهة interface
Section titled “الواجهة interface”في لعبة، اللاعب يتلقى الضرر، والصندوق الخشبي يتلقى الضرر، والجدار كذلك. لا قرابة بينها، لكن تجمعها قدرة واحدة. الواجهة (Interface) تصف قدرة يتعهد الكلاس بتنفيذها:
interface IDamageable{ int Health { get; } void TakeDamage(int amount);}
interface IDescribable{ string Describe();}
class Crate : IDamageable, IDescribable{ public int Health { get; private set; } = 30;
public void TakeDamage(int amount) { Health = Math.Max(0, Health - amount); if (Health == 0) Console.WriteLine("تحطم الصندوق!"); }
public string Describe() => $"صندوق خشبي، صحته {Health}";}var crate = new Crate();Console.WriteLine(crate.Describe());
IDamageable target = crate;target.TakeDamage(20);Console.WriteLine(target.Health);target.TakeDamage(20);صندوق خشبي، صحته 3010تحطم الصندوق!- الكلاس يرث من كلاس أب واحد فقط، لكنه ينفّذ أي عدد من الواجهات مفصولة بفواصل.
- أعضاء الواجهة
publicتلقائياً، وتنفيذها في الكلاس يُكتبpublicبدونoverride. - الواجهة قد تطلب دوالاً أو خصائص مثل
Health. - العُرف في C# أن يبدأ اسم الواجهة بالحرف
I: مثلIDamageableوIComparable.
الواجهة مثل مقبس الكهرباء: أي جهاز له القابس المناسب يعمل، ولا يهم المقبس هل هو مصباح أم ثلاجة.
فحص النوع: is و switch
Section titled “فحص النوع: is و switch”متغير من نوع الأب قد يحمل كائناً من أي ابن. الكلمة is تفحص النوع الحقيقي وتعرّف متغيراً منه في خطوة واحدة:
object[] items = { "نص", 42, 3.5 };foreach (object item in items){ if (item is string s) { Console.WriteLine($"نص طوله {s.Length}"); } else if (item is int n) { Console.WriteLine($"عدد صحيح ضعفه {n * 2}"); } else { Console.WriteLine($"نوع آخر: {item}"); }}نص طوله 2عدد صحيح ضعفه 84نوع آخر: 3.5كل الأنواع في C# ترث من object، ولذلك قبلت المصفوفة أي شيء. ويمكن كتابة الفحص نفسه بشكل أقصر بتعبير switch:
static string Describe(object item) => item switch{ string s => $"نص طوله {s.Length}", int n when n < 0 => "عدد سالب", int n => $"عدد صحيح ضعفه {n * 2}", null => "لا شيء", _ => "نوع آخر",};
Console.WriteLine(Describe("مرحباً"));Console.WriteLine(Describe(-5));Console.WriteLine(Describe(21));Console.WriteLine(Describe(true));نص طوله 6عدد سالبعدد صحيح ضعفه 42نوع آخرالفروع تُفحص بالترتيب، و when تضيف شرطاً إضافياً، والشرطة السفلية _ تعني «أي شيء آخر».
sealed: ممنوع الوراثة
Section titled “sealed: ممنوع الوراثة”كلمة sealed أمام كلاس تمنع أي كلاس آخر من الوراثة منه:
sealed class Token{ public string Value { get; } = "abc123";}الكلاس string نفسه sealed، فلا يستطيع أحد أن يرث منه ويغيّر سلوكه.
كلاس مجرد أم واجهة؟
Section titled “كلاس مجرد أم واجهة؟”| الكلاس المجرد | الواجهة | |
|---|---|---|
| العلاقة | «هو نوع من» | «يستطيع أن» |
| العدد المسموح | أب واحد | أي عدد |
| تخزين بيانات | نعم، حقول ومُنشئ | لا تخزن حالة |
| مثال | Shape و Circle |
IDamageable و IComparable |
القاعدة العملية: ابدأ بالواجهة، واختر الكلاس المجرد فقط إذا كان الأبناء يشتركون في بيانات وكود فعلي.
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ
abstract class Vehicleفيه خاصيةNameودالة مجردةWheels()ودالة عاديةInfo(). - أنشئ
CarوBikeيرثان منه، وضعهما في مصفوفة واطبع معلوماتهما. - أنشئ
interface IElectricفيهاvoid Charge()، وكلاسElectricCarيرث منVehicleوينفّذIElectric. - تحدٍّ: مرّ على مصفوفة المركبات، واشحن فقط ما ينفّذ
IElectricباستخدامis.
أخطاء شائعة
Section titled “أخطاء شائعة”- إنشاء كائن من كلاس مجرد:
new Shape(...)يعطي:Cannot create an instance of the abstract type or interface 'Shape'. - نسيان override: كتابة الدالة في الابن بدون
overrideلا تنفّذ الدالة المجردة، فيرفض المترجم الكلاس. - نسيان عضو من الواجهة: ستقرأ
does not implement interface member. الواجهة عقد، وكل بنوده إلزامية. - تنفيذ غير public: أعضاء الواجهة عامة، فتنفيذها في الكلاس يجب أن يكون
public.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
كم واجهة يستطيع الكلاس الواحد أن ينفّذ؟
الكلاس يرث من كلاس أب واحد، لكنه ينفّذ أي عدد من الواجهات.
سؤال
ماذا يحدث إذا ورث كلاس عادي من كلاس مجرد ولم يكتب دالته المجردة؟
الدوال المجردة إلزامية: إما أن ينفّذها الابن بـ override أو يكون هو نفسه abstract.
الخلاصة
Section titled “الخلاصة”abstract classلا يُنشأ منه كائن، ودواله المجردة يلتزم بها الأبناء بـoverride.interfaceتصف قدرة، واسمها يبدأ بـI، والكلاس ينفّذ أي عدد منها.- تعدد الأشكال: الكائن الحقيقي هو من يحدد نسخة الدالة التي تعمل.
isوتعبيرswitchيفحصان النوع ويعرّفان متغيراً منه.sealedتمنع الوراثة من الكلاس.