علاقة "يحتوي على" (Has-A)
جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 49 من 70.
خلال هذه الدورة، استخدمتَ الوراثة لمشاركة السلوك بين الأصناف — فـ Dog "هو" Animal. لكن الوراثة ليست دائمًا الأداة الصحيحة.
في بعض الأحيان، لا تشترك الكائنات في شجرة عائلة؛ بدلاً من ذلك، ببساطة يحتوي كائن واحد على كائن آخر. يُسمى هذا التركيب (composition)، وهو يمثل علاقة "لديه-واحد" (has-a).
فكر في سيارة. السيارة ليست نوعاً من المحركات—بل هي تمتلك محركاً. المحرك هو كائن منفصل موجود داخل السيارة:
local Engine = {}
Engine.__index = Engine
function Engine:new(horsepower)
local obj = {horsepower = horsepower}
setmetatable(obj, Engine)
return obj
end
local Car = {}
Car.__index = Car
function Car:new(model, engine)
local obj = {
model = model,
engine = engine -- تخزين كائن Engine داخل Car
}
setmetatable(obj, Car)
return obj
endعند إنشاء سيارة، تقوم بتمرير كائن محرك (engine object) موجود إلى المنشئ (constructor) الخاص بها:
local myEngine = Engine:new(200)
local myCar = Car:new("Sedan", myEngine)
print(myCar.engine.horsepower) -- 200لا ترث السيارة من المحرك—بل تحتفظ ببساطة بمرجع له. وهذا يحافظ على استقلالية كلا الـ classes. يمكنك استبداله بمحرك مختلف، أو استخدام نفس الـ engine class في دراجة نارية.
يمنحك التركيب (Composition) مرونة لا تستطيع الوراثة (inheritance) توفيرها: فأنت تقوم بتجميع الكائنات من أجزاء بدلاً من حصرها في تسلسل هرمي عائلي صارم.
التحدي
سهللنقم ببناء نظام كمبيوتر باستخدام التركيب (composition)! بدلاً من الوراثة، ستقوم بإنشاء Computer يحتوي على CPU بداخله—مما يوضح علاقة "has-a".
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
CPU.lua: قم بإنشاء فئة CPU مع منشئ:new(brand, speed)يقوم بتخزين اسم العلامة التجارية والسرعة بوحدة GHz. قم بتضمين طريقة:getInfo()التي تعيد سلسلة نصية بالتنسيق التالي:{brand} @ {speed}GHzComputer.lua: قم بإنشاء فئة Computer يقبل منشئها:new(model, cpu)اسم الطراز وكائن CPU. يقوم الـ Computer بتخزين كائن CPU بداخله (التركيب!). قم بتضمين طريقة:describe()التي تطبع طراز الكمبيوتر ومعلومات المعالج الخاص به.main.lua: اجمع كل شيء معاً! اقرأ المدخلات لعلامة المعالج التجارية، وسرعة المعالج، وطراز الكمبيوتر. قم بإنشاء كائن CPU أولاً، ثم مرره إلى منشئ Computer. أخيراً، استدعِ طريقة:describe()الخاصة بالكمبيوتر.
ستتلقى ثلاثة مدخلات:
- علامة المعالج التجارية (سلسلة نصية)
- سرعة المعالج بوحدة GHz (رقم)
- اسم طراز الكمبيوتر (سلسلة نصية)
يجب أن يظهر مخرجك وصف الكمبيوتر، والذي يتضمن معلومات من المعالج المحتوى بداخله:
Model: {model}
CPU: {brand} @ {speed}GHzعلى سبيل المثال، إذا كانت المدخلات هي Intel و 3.5 و WorkStation، فيجب أن يكون المخرج:
Model: WorkStation
CPU: Intel @ 3.5GHzلاحظ كيف أن Computer لا يرث من CPU—بل يحتفظ ببساطة بمرجع إليه. تصل طريقة :describe() الخاصة بالـ Computer إلى بيانات CPU من خلال الكائن المخزن، مما يوضح كيف يتيح لك التركيب بناء كائنات معقدة من أجزاء أبسط!
ورقة مرجعية
يمكن للكائنات أن ترتبط من خلال التركيب (composition)، والذي يمثل علاقة "يحتوي على" (has-a). بدلاً من الوراثة، يحتوي كائن واحد على كائن آخر كمكون.
مثال على التركيب مع سيارة تحتوي على محرك:
local Engine = {}
Engine.__index = Engine
function Engine:new(horsepower)
local obj = {horsepower = horsepower}
setmetatable(obj, Engine)
return obj
end
local Car = {}
Car.__index = Car
function Car:new(model, engine)
local obj = {
model = model,
engine = engine -- تخزين كائن المحرك داخل السيارة
}
setmetatable(obj, Car)
return obj
endإنشاء واستخدام الكائنات المركبة:
local myEngine = Engine:new(200)
local myCar = Car:new("Sedan", myEngine)
print(myCar.engine.horsepower) -- 200يوفر التركيب مرونة من خلال تجميع الكائنات من أجزاء بدلاً من استخدام تسلسلات هرمية جامدة للوراثة. تظل المكونات مستقلة ويمكن إعادة استخدامها عبر فئات مختلفة.
جرّب بنفسك
-- main.lua
-- تجميع كل شيء معاً باستخدام التركيب (composition)
local CPU = require('CPU')
local Computer = require('Computer')
-- قراءة المدخلات
local cpuBrand = io.read()
local cpuSpeed = tonumber(io.read())
local computerModel = io.read()
-- TODO: إنشاء كائن CPU مع العلامة التجارية والسرعة
-- TODO: إنشاء كائن Computer مع الطراز وكائن CPU
-- TODO: استدعاء طريقة describe() الخاصة بالكمبيوتر لطباعة المخرجات
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
4مشروع: بنك رقمي
إعداد المشروعطريقة الإيداع7تعدد الأشكال وإعادة التعريف
إعادة تعريف الدوالاستدعاء دوال الفئة الأبDuck Typingواجهة مشتركةالتحقق من النوعمراجعة - أدوار الموظفين2نمط النموذج الأولي للفئات
مفهوم النموذج الأوليالربط باستخدام __indexالمنشئ :new()تهيئة الخصائصنسخ مستقلةملخص - مصنع السيارات5تحميل المعاملات في OOP
جمع الكائناتطرح الكائناتدمج الكائناتمقارنة الكائنات (<, >)مراجعة - رياضيات المحفظة8التغليف
اصطلاحات التسميةاستخدام Closures للخصوصيةالوصول عبر Closuresجداول للقراءة فقطمنطق التحقق من الصحةمراجعة - الخزنة الآمنة11أنماط التصميم (مبسط)
دوال المصنعجدول Singletonنمط الـ Iteratorالمراقب (المستمع)مراجعة - مصنع الـ Logger3حالة الكائن وسلوكه
متغيرات النسخةدالات الجلب (Getter Methods)دالات التعيين (Setter Methods)الخصائص المحسوبةتنسيق النصوصالتحقق من المساواةمراجعة - درجة الطالب6أساسيات الوراثة
إعداد الوراثةوراثة الـ Methodsتوسيع الـ Constructorإضافة Methods للابنالمشترك مقابل الفريدملخص - التسلسل الهرمي للأشكال9التركيب و Mixins
علاقة "يحتوي على" (Has-A)التفويض (Delegation)Mixins بسيطةتطبيق Mixins متعددةMixins مقابل الوراثةملخص - تجميع الروبوت