يُطلق على قدرة الفئة (الصنف) على اشتقاق الخصائص من صنف آخر اسم الوراثة. الوراثة هي واحدة من أهم ميزات البرمجة غرضية التوجه OOP.
الفئة الابن (Sub Class): الفئة التي ترث الخصائص من فئة أخرى تسمى الفئة الفرعية أو الفئة المشتقة أو الفئة الابن.
الفئة الأب (Super Class): تسمى الفئة التي تورّث خصائصها إلى فئة فرعية فئة الأساس أو الفئة العليا أو الفئة الأب.
وبشكل أكثر وضوح يمكننا القول أن الوراثة هي تضمين محتوى صنف في صنف آخر (احتواء)، أي أنه يحصل على الدوال و المتغيرات الموجودة فيه.
كمثال، ضع في اعتبارك مجموعة من المركبات، وتحتاج إلى إنشاء أصناف للحافلات والسيارات والشاحنات. ستكون الوظائف `مقدار_الوقود` و `السعة` و `استخدم_الفرامل` هي نفسها لجميع الأصناف الثلاثة. بالتالي إذا أنشأنا هذه الأصناف بدون استخدام مفهوم الوراثة، فعلينا كتابة كل هذه الوظائف في كل صنف من الأصناف الثلاث
يمكنك أن ترى بوضوح أن العملية المذكورة أعلاه تؤدي إلى تكرار نفس الشيفرات (الأكواد أو التعليمات) 3 مرات، و هذا يزيد من فرص الخطأ وتكرار البيانات. لتجنب هذا النوع من الحالات، يتم استخدام الوراثة.
أما إذا أنشأنا صنف خاص يمثل مركبة وكتبنا هذه الوظائف فيها ثم جعلنا باقي الأصناف ترث منها، فيمكننا ببساطة تجنب تكرار البيانات (سنكتبها مرة واحدة فقط) وزيادة قابلية إعادة الاستخدام.
- الوراثة في الأسس قد تكون غريبة بعض الشيء وذلك لأن الأسس منخفضة المستوى (نعم، منخفضة أكثر حتى من السي++) وبالتالي هي توفر للمستخدم اللبنات الأساسية التي يمكن استخدامها لتطبيق مفهوم الوراثة (إلى جانب أمور أخرى). اللبنة الأساسية التي نستخدمها في الأسس لتطبيق الوراثة هي المبدل `@حقنة` (@injection). لكي تعرّف في الأسس صنفاً يرث من صنف آخر كل ما عليك فعله هو تعريف متغير من الصنف الأب في بداية الصنف الابن وإضافة `@حقنة` إليه وهكذا نكون قد قمنا بوراثة الأب:
صنف الـصنف_الابن { @حقنة عرف الصنف_الأب: الـصنف_الأب؛ }
class Sub_Class { @injection def super_class: Super_Class; }
كي تفهم التعريف أعلاه تحتاج أن تفهم الطريقة التي تطبق فيها لغات البرمجة الوراثة خلف الكواليس. عندما تعرف صنفًا مشتقًا من صنف آخر في لغات أخرى كالسي++ فما يفعله المترجم هو إضافة عنصر من صنف الأب إلى الصنف الابن (أي يضمن الأب داخل الابن) ومن ثم جعل عناصر الأب متوفرة للولوج المباشر في الابن كما لو أنها كتبت في الابن مباشرة وليس في متغير داخل الابن. هذا بالضبط ما نفعله هنا؛ نحن نخبر المترجم أن يضيف الصنف الأب كعنصر ضمن الابن ثم نخبر المترجم باستخدام المبدل `@حقنة` أن يحقن كل التعريفات التي في مجال الأب ضمن المجال الذي يحتويه (أي الابن). بمعنى آخر، لو كان في الأب عنصر اسمه `عنصر1` ثم كتبنا `ابن.عنصر1` فإن المترجم سيحولها تلقائيًا إلى `ابن.أب.عنصر1`. هذا ببساطة ما تفعله المترجمات المختلفة ونحن هنا نخبر المترجم أن يفعل الشيء بالتفصيل. مستقبلاً يمكن إضافة أوامر مساعدة أو ماكروهات تسهل لنا هذا التعريف، أو يمكن للمستخدم فعل ذلك بنفسه.
في الأسس يمكنك تطبيق أي شكل من أشكال الوراثة.
صنف ب يرث خصائص صنف ا.
في المثال التالي قمنا بتعريف صنف اسمه `والـد` (Parent) يحتوي على متغير اسمه `س` (x) و دالة اسمها `اطبع_رسالة` (printMessage).
بعدها قمنا بإنشاء صنف اسمه `ولـد` (Child) يحتوي على متغير إسمه `ص` (y) و يرث من الصنف `والـد`.
// الوراثة الفردية اشمل "مـتم/طـرفية"؛ استخدم مـتم؛ // تعريف الصنف الأب. صنف والـد { عرف س: صحيح = 5؛ عملية هذا.اطبع_رسالة() { طـرفية.اطبع("رسالة من الصنف والـد")؛ } } // تعريف الصنف الابن. صنف ولـد { @حقنة عرف والد: والـد؛ عرف ص: صحيح = 8؛ } // إنشاء كائن من الصف الابن. عرف كائن: ولـد؛ // طباعة المتغير ص الذي تم تعريفه في الصف الابن طـرفية.اطبع("ص = %d\ج", كائن.ص)؛ // الذي تم تعريفه في الصف الأب والذي تمت وراثته من الابن x طباعة المتغير طـرفية.اطبع("س = %d\ج", كائن.س)؛ // استدعاء الدالة اطبع_رسالة التي تم تعريفها في الصف الأب وتمت وراثتها أيضاً من الأب كائن.اطبع_رسالة()؛ /* y = 8 x = 5 Hello from class Parent */
// Single inheritance import "Srl/Console.alusus"; use Srl; // تعريف الصنف الأب. class Parent { def x: int = 5; handler this.printMessage() { Console.print("Hello from class Parent \n"); } } // تعريف الصنف الابن. class Child { @injection def obj: Parent; def y: int = 8; } // إنشاء كائن من الصف الابن. def objChild: Child; // الذي تم تعريفه في الصف الابن y طباعة المتغير Console.print("y = %d\n", objChild.y); // الذي تم تعريفه في الصف الأب والذي تمت وراثته من الابن x طباعة المتغير Console.print("x = %d\n", objChild.x); // التي تم تعريفها في الصف الأب وتمت وراثتها أيضاً من الابن printMessage استدعاء الدالة objChild.printMessage(); /* y = 8 x = 5 Hello from class Parent */
في الأسس، تُعطى الأولوية في الولوج للمعطيات والدالات إلى المجال الحالي قبل المجالات الأخرى. أي بمعنى آخر، إذا كان هناك دالة هات() في الصنف الأب ودالة أخرى بنفس الاسم في الصنف الابن وتم استدعاءها، فسيتم تنفيذ الدالة الموجودة في الصنف الابن وليس الأب.
لاحظ المثال التالي الذي يوضح هذه الفكرة:
اشمل "مـتم.طـرفية"؛ استخدم مـتم.طـرفية؛ صنف والـد { عرف x: صحيح = 5؛ عملية هذا.اطبع_رسالة() { اطبع("رسالة من الصنف والـد \n")؛ } } صنف ولـد { @حقنة عرف والد: والـد؛ عرف ص: صحيح = 8؛ عملية هذا.اطبع_رسالة() { اطبع("رسالة من الصنف ولـد \n")؛ } } عرف ولد: ولـد؛ ولد.اطبع_رسالة()؛ /* رسالة من الصنف ولـد */ // لاحظ أنه ستُطبع الرسالة الموجودة في الصنف الابن // وليس الأب، لأن الأولوية للمجال الحالي
import "Srl/Console.alusus"; use Srl.Console; class Parent { def x: int = 5; handler this.printMessage() { print("Hello from class Parent \n"); } } class Child { @injection def parent: Parent; def y: int = 8; handler this.printMessage() { print("Hello from class Child \n"); } } def objChild: Child; objChild.printMessage(); /* Hello from class Child */ // لاحظ أنه ستُطبع الرسالة الموجودة في الصنف الابن // وليس الأب، لأن الأولوية للمجال الحالي
صنف ا يرثه صنف ب، والصنف ب بدوره يرثه صنف آخر وليكن ج (صنف مُشتق يرث صنفًا مُشتقًا آخر).
في المثال التالي سنقوم بتعريف صنف `مـركبة`، ثم سنقوم بوراثته من قبل الصنف ربـاعي_الدفع، ثم سنقوم بوراثة الأخير من الصنف سـيارة.
// وراثة متعددة المستويات اشمل "مـتم/طـرفية"؛ استخدم مـتم.طـرفية؛ صنف مـركبة { عملية هذا~هيئ() { اطبع("هذه مـركبة\n")؛ } } // صنف ابن يرث من الصنف السابق. صنف ربـاعي_الدفع { @حقنة عرف مركبة: مـركبة؛ عملية هذا~هيئ() { اطبع("هذه مركبة رباعية الدفع\n")؛ } } // صنف يرث الصنف ربـاعي_الدفع (مـركبة الذي يرث بدوره) صنف سـيارة { @حقنة عرف رباعي_الدفع: ربـاعي_الدفع؛ عملية هذا~هيئ() { اطبع("هذه سيارة")؛ } } // دالة رئيسية { عرف سيارة: سـيارة؛ // سيؤدي إلى استدعاء بواني الآباء أيضًا } رئيسية()؛ /* هذه مركبة هذه مركبة رباعية الدفع هذه سيارة */
// Multilevel Inheritance import "Srl/Console.alusus"; use Srl.Console; class Vehicle { handler this~init() { print("This is a Vehicle\n"); } } // صنف ابن يرث من الصنف السابق. class FourWheeler { @injection def vehicle: Vehicle; handler this~init() { print("Objects with 4 wheels are vehicles\n"); } } // (Vehicle الذي يرث بدوره) FourWheeler صنف يرث الصنف class Car { @injection def fourWheeler: FourWheeler; handler this~init() { print("Car has 4 Wheels"); } } // func main{ def objCar: Car; // سيؤدي إلى استدعاء بواني الآباء Car إنشاء كائن من الصنف } main() /* This is a Vehicle Objects with 4 wheels are vehicles Car has 4 Wheels */
لاحظ أن إنشاء كائن من الصنف سـيارة أدى إلى استدعاء باني الصنف ربـاعي_الدفع والذي بدوره أدى إلى استدعاء باني الصنف مـركبة.
صنف ج يرث من صنف ا و ب.
في المثال التالي سيرث الصنف سـيارة كل من الصنفين مـركبة و ربـاعي_الدفع.
اشمل "مـتم/طـرفية"؛ استخدم مـتم.طـرفية؛ صنف مـركبة { عملية هذا~هيئ() { اطبع("هذه مـركبة\n")؛ } } صنف ربـاعي_الدفع { عملية هذا~هيئ() { اطبع("هذا رباعي الدفع\ج")؛ } } // صنف سـيارة يرث الصنفين ربـاعي_الدفع و مـركبة صنف سـيارة { @حقنة عرف رباعي_الدفع: ربـاعي_الدفع؛ @حقنة عرف مركبة: مـركبة؛ عملية هذا~هيئ() { اطبع("هذه سيارة\ج")؛ } }؛ دالة رئيسية { عرف سيارة :سـيارة؛ } رئيسية()؛ /* هذا رباعي الدفع هذه مركبة هذه سيارة */
import "Srl/Console.alusus"; use Srl.Console; class Vehicle { handler this~init() { print("This is a Vehicle\n"); } } class FourWheeler { handler this~init() { print("Objects with 4 wheels are vehicles\n"); } } // FourWheeler و Vehicle يرث الصنفين Car صنف class Car { @injection def fourWheeler: FourWheeler; @injection def vehicle: Vehicle; handler this~init() { print("Car has 4 Wheels"); } }; func main{ def objCar:Car; } main(); /* Objects with 4 wheels are vehicles This is a Vehicle Car has 4 Wheels */
لاحظ أنه قام باستدعاء باني الصنف `ربـاعي_الدفع` (FourWheeler) أولاً، وهذا منطقي لأننا قمنا بوراثته أولاً.
عدة أصناف ترث من نفس الصنف. على سبيل المثال صنف ج و ب يرثان الصنف ا.
في المثال التالي سنقوم بوراثة الصنف مـركبة من قبل الصنفين سـيارة و ربـاعي_الدفع.
اشمل "مـتم/طـرفية"؛ استخدم مـتم.طـرفية؛ صنف مـركبة { عملية هذا~هيئ() { اطبع("هذه مـركبة\ج")؛ } } // مـركبة يرث من الصنف ربـاعي_الدفع صنف صنف ربـاعي_الدفع { @حقنة عرف مركبة: مـركبة؛ عملية هذا~هيئ() { اطبع("هذه مركبة رباعية الدفع\ج")؛ } } // صنف سـيارة يرث الصنف مـركبة صنف سـيارة { @حقنة عرف مركبة: مـركبة؛ عملية هذا~هيئ() { اطبع("هذه سيارة")؛ } } دالة رئيسية { عرف كائن1: ربـاعي_الدفع؛ عرف كائن2: سـيارة؛ } رئيسية()؛ /* هذه مركبة هذه مركبة رباعية الدفع هذه مركبة هذه سيارة */
import "Srl/Console.alusus"; use Srl.Console; class Vehicle { handler this~init() { print("This is a Vehicle\n"); } } // Vehicle يرث من الصنف FourWheeler صنف class FourWheeler { @injection def obj: Vehicle; handler this~init() { print("Objects with 4 wheels are vehicles\n"); } } // Vehicle يرث الصنف Car صنف class Car { @injection def obj: Vehicle; handler this~init() { print("Car has 4 Wheels"); } } func main { def obj1: FourWheeler; def obj2: Car; } main(); /* This is a Vehicle Objects with 4 wheels are vehicles This is a Vehicle Car has 4 Wheels */
يمكنك دمج أي شكل من أشكال الوراثة السابقة مع بعضها البعض.
اشمل "مـتم/طـرفية"؛ استخدم مـتم.طـرفية؛ صنف مـركبة { عملية هذا~هيئ() { اطبع("هذه مـركبة\ج")؛ } } صنف تـعريفة { عملية هذا~هيئ() { اطبع("تعريفة الركوب\ج")؛ } } // صنف سـيارة يرث الصنف مـركبة صنف سـيارة { @حقنة عرف مركبة: مـركبة؛ عملية هذا~هيئ() { اطبع("هذه سيارة")؛ } } // صنف حـافلة يرث الصنفين تـعريفة و مـركبة صنف حـافلة { @حقنة عرف مركبة: مـركبة؛ @حقنة عرف تعريفة: تـعريفة؛ }؛ دالة رئيسية { عرف حافلة: حـافلة؛ } رئيسية()؛ /* هذه مركبة تعريفة الركوب */
import "Srl/Console.alusus"; use Srl.Console; class Vehicle { handler this~init() { print("This is a Vehicle\n"); } } class Fare { handler this~init() { print("Fare of Vehicle\n"); } } // Vehicle يرث الصنف Car صنف class Car { @injection def obj:Vehicle; handler this~init() { print("Car has 4 Wheels"); } } // Fare و Vehicle يرث من الصنف Bus صنف class Bus { @injection def obj: Vehicle; @injection def obj: Fare; }; func main { def obj: Bus; } main(); /* This is a Vehicle Fare of Vehicle */