Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

توسيع الـ Constructor

جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 33 من 70.

عندما ترث فئة فرعية (child class) من فئة أب (parent class)، غالباً ما ترغب في أن يقوم منشئ الفئة الفرعية (child's constructor) بتهيئة كل ما تقوم الفئة الأب بتهيئته—بالإضافة إلى أي حقول إضافية خاصة بالفئة الفرعية. وبدلاً من تكرار الكود، يمكنك استدعاء منشئ الفئة الأب (parent's constructor) مباشرةً.

تتضمن هذه التقنية استخدام اسم الفئة الأب (parent class) لاستدعاء تابعها :new()، مع تمرير الفئة الأب كمعامل أول. يمكنك القيام بذلك باستخدام صيغة النقطة (dot syntax): Vehicle.new(Vehicle, speed)، أو ما يعادلها باستخدام صيغة النقطتين (colon syntax): Vehicle:new(speed) — كلاهما صالحان وينتجان نفس النتيجة. يؤدي هذا إلى إنشاء الكائن الأساسي مع كافة عمليات تهيئة الأب، والتي تقوم بعد ذلك بتخصيصها للفئة الابنة (child).

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(speed)
    local obj = {speed = speed}
    setmetatable(obj, Vehicle)
    return obj
end

-- Car ترث من Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- استدعاء منشئ الفئة الأب
    obj.brand = brand                         -- إضافة حقل خاص بالفئة الابنة
    setmetatable(obj, Car)                    -- إعادة الربط بـ Car
    return obj
end

لاحظ الخطوات الرئيسية في منشئ الفئة الابنة (child constructor): أولاً، نقوم باستدعاء Vehicle.new(Vehicle, speed) (أو ما يعادلها Vehicle:new(speed)) للحصول على كائن يحتوي على حقل speed تم تعيينه بالفعل. ثم نضيف حقل brand الخاص بالفئة الابنة. وأخيراً، نقوم بتغيير الـ metatable من Vehicle إلى Car لكي يبحث الكائن عن الدوال في الفئة الابنة أولاً.

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- الناتج: 120
print(myCar.brand)  -- الناتج: Toyota

يحافظ هذا النمط على كودك DRY - حيث يتواجد منطق التهيئة المشترك في الـ parent، بينما يتعامل كل child فقط مع سماته الفريدة.

challenge icon

التحدي

سهل

لنقم ببناء تسلسل هرمي للكائنات حيث تمدد الفئة الابنة (child class) منشئ الفئة الأب (parent class)! ستقوم بإنشاء فئة أب باسم Animal وفئة ابنة باسم Bird، حيث يستدعي منشئ Bird منشئ Animal للتعامل مع التهيئة المشتركة قبل إضافة سمته الفريدة الخاصة.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • Animal.lua: قم بتعريف الفئة الأب باستخدام منشئ :new(name) الذي يخزن اسم الحيوان. قم بتضمين تابع :describe() يطبع اسم الحيوان. قم بإعداد نمط الفئة القياسي باستخدام __index وأعد الفئة.
  • Bird.lua: قم بتعريف الفئة الابنة التي ترث من Animal. يجب أن يستدعي منشئ :new(name, wingspan) الخاص بـ Bird منشئ الأب باستخدام Animal.new(Animal, name) لتهيئة الاسم، ثم إضافة حقل wingspan إلى الكائن، وأخيراً إعادة ربط الـ metatable بـ Bird. قم بتضمين تابع :info() يطبع كلاً من الاسم وطول جناحي الطائر بتنسيق محدد.
  • main.lua: اطلب وحدة Bird، واقرأ مدخلين (الاسم وطول الجناحين)، وأنشئ مثيلاً (instance) من Bird، واستدعِ كلا التابعين لإثبات أن الوظائف الموروثة والخاصة بالابن تعمل معاً.

ستتلقى مدخلين:

  1. اسم الطائر
  2. طول جناحي الطائر (رقم)

في ملفك الرئيسي، أنشئ Bird بالاسم وطول الجناحين المعطيين. ثم استدعِ :describe() متبوعاً بـ :info() في أسطر منفصلة.

يجب أن يطبع التابع :describe() (الموروث من Animal):

Animal: {name}

يجب أن يطبع التابع :info() (المعرف في Bird):

{name} has a wingspan of {wingspan}

على سبيل المثال، إذا كانت المدخلات هي Eagle و 200، يجب أن يكون الناتج:

Animal: Eagle
Eagle has a wingspan of 200

المفتاح هنا هو أن منشئ Bird لا يكرر تهيئة الاسم — بل يفوض هذا العمل لمنشئ Animal، ثم يضيف فقط ما هو فريد للطيور. هذا يحافظ على كودك DRY (لا تكرر نفسك) مع بناء سلسلة وراثة صحيحة!

ورقة مرجعية

لاستدعاء منشئ الفئة الأب (parent class constructor) من فئة تابعة (child class)، استخدم اسم الفئة الأب مع صيغة النقطة (dot syntax)، مع تمرير الفئة الأب نفسها كمعامل أول:

local obj = ParentClass.new(ParentClass, parentArgs)

بعد استدعاء منشئ الفئة الأب، أضف الحقول الخاصة بالفئة التابعة وأعد ربط الجدول الميتا (metatable) بالفئة التابعة:

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(speed)
    local obj = {speed = speed}
    setmetatable(obj, Vehicle)
    return obj
end

local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- استدعاء منشئ الفئة الأب
    obj.brand = brand                         -- إضافة حقل خاص بالفئة التابعة
    setmetatable(obj, Car)                    -- إعادة الربط بـ Car
    return obj
end

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- المخرجات: 120
print(myCar.brand)  -- المخرجات: Toyota

يحافظ هذا النمط على منطق التهيئة وفق مبدأ DRY—حيث توجد الإعدادات المشتركة في الفئة الأب، بينما تتعامل الفئات التابعة فقط مع سماتها الفريدة.

جرّب بنفسك

-- main.lua: نقطة الدخول

local Bird = require('Bird')

-- قراءة المدخلات
local name = io.read()
local wingspan = tonumber(io.read())

-- TODO: إنشاء مثيل (instance) من Bird بالاسم وطول الجناح المعطيين

-- TODO: استدعاء :describe() على الطائر (موروثة من Animal)

-- TODO: استدعاء :info() على الطائر (معرفة في Bird)
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming