Mixins مقابل الوراثة
جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 53 من 70.
لقد تعلمت طريقتين لمشاركة السلوك: الوراثة (inheritance) تنشئ علاقة "is-a" من خلال سلسلة النماذج الأولية (prototype chain)، بينما تقوم الـ mixins بنسخ الدوال مباشرة إلى الفئة (class). ولكن متى يجب عليك استخدام كل نهج؟
استخدم الوراثة عندما تتشارك الكائنات هوية أساسية. فالطائر Bird هو حيوان Animal—ومن المنطقي أن يرث Bird سلوكيات الحيوان الأساسية مثل :eat() أو :sleep(). هذه العلاقة دائمة وتحدد ماهية الكائن بشكل أساسي.
استخدم mixins عندما تريد إضافة قدرة لا تُعرّف الهوية الأساسية للكائن.
ليست كل الطيور قادرة على الطيران—فالبطاريق والنعام طيور، لكنها لا تطير. الطيران هو قدرة، وليس سمة محددة لكون الكائن طائراً. تسمح لك الـ mixin المسماة Flyable بإضافة ميزة الطيران للطيور التي تحتاجها، دون فرضها على جميع الطيور.
يعمل النهجان معًا بشكل رائع:
-- يرث Bird من Animal (علاقة is-a)
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})
-- Flyable هي قدرة (mixin)
local Flyable = {}
function Flyable:fly()
print(self.name .. " soars through the sky!")
end
-- تطبيق الـ mixin فقط على الطيور التي يمكنها الطيران
applyMixin(Bird, Flyable)يمنحك هذا النمط أفضل ما في العالمين: تسلسل هرمي واضح للفئات من أجل الهوية المشتركة، بالإضافة إلى قدرات مرنة يمكن دمجها عند الحاجة.
التحدي
سهللنقم ببناء نظام بيئي صغير من الكائنات يوضح متى نستخدم الوراثة (inheritance) مقابل الخلطات (mixins)! ستقوم بإنشاء فئة أساسية Animal، وفئة Bird ترث منها، وخلطة Swimmable—لأنه بينما كل الطيور حيوانات، لا تستطيع كل الطيور السباحة.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Animal.lua: أنشئ فئة Animal الأساسية مع منشئ:new(name)يقوم بتخزين الاسم. قم بتضمين تابع:eat()يطبع{name} is eating. يمثل هذا الهوية الجوهرية التي تشترك فيها جميع الحيوانات.Swimmable.lua: أنشئ خلطة (mixin) مع تابع:swim()يطبع{self.name} is swimming. هذه قدرة—ليست كل الكائنات تستطيع السباحة، لذا لا ينبغي أن تكون جزءاً من سلسلة الوراثة.Bird.lua: أنشئ فئة Bird ترث من Animal. يجب أن يستدعي المنشئ:new(name, canSwim)منشئ الفئة الأب ويخزن ما إذا كان هذا الطائر يستطيع السباحة أم لا. قم بتطبيق خلطة Swimmable على فئة Bird. قم بتضمين تابع:chirp()يطبع{name} chirps!—هذا سلوك خاص بالطيور.main.lua: اكتب الدالة المساعدةapplyMixinهنا. اقرأ المدخلات لاسم الطائر وما إذا كان بإمكانه السباحة (yesأوno). أنشئ مثيلاً (instance) من Bird، ثم استعرض قدراته: استدعِ:eat()(الموروث من Animal)، واستدعِ:chirp()(تابع خاص بـ Bird)، واستدعِ:swim()فقط إذا كان الطائر يستطيع السباحة.
ستتلقى مدخلين:
- اسم الطائر (سلسلة نصية)
- ما إذا كان الطائر يستطيع السباحة:
yesأوno
إذا كان الطائر يستطيع السباحة، يجب أن يكون مخرجك:
{name} is eating
{name} chirps!
{name} is swimmingإذا كان الطائر لا يستطيع السباحة، يجب أن يكون مخرجك:
{name} is eating
{name} chirps!على سبيل المثال، إذا كانت المدخلات هي Duck و yes، يجب أن يكون المخرج:
Duck is eating
Duck chirps!
Duck is swimmingإذا كانت المدخلات هي Sparrow و no، يجب أن يكون المخرج:
Sparrow is eating
Sparrow chirps!يوضح هذا التحدي التمييز الرئيسي: الطائر هو حيوان (وراثة)، لكن السباحة هي قدرة اختيارية (mixin). يتم تطبيق الخلطة على الفئة، لكن منطقك الأساسي يقرر ما إذا كان سيستخدمها بناءً على قدرات الطائر الفردي!
ورقة مرجعية
استخدم الوراثة (inheritance) عندما تتشارك الكائنات هوية أساسية (علاقة "هو-يكون"). استخدم الـ mixins عند إضافة قدرات لا تحدد الهوية الجوهرية للكائن.
تنشئ الوراثة علاقة دائمة عبر سلسلة النماذج الأولية (prototype chain):
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})تضيف الـ mixins قدرات اختيارية يمكن تطبيقها بشكل انتقائي:
local Flyable = {}
function Flyable:fly()
print(self.name .. " soars through the sky!")
end
applyMixin(Bird, Flyable)يعمل النهجان معاً: استخدم الوراثة للهوية المشتركة والـ mixins للقدرات المرنة التي تحتاجها بعض الحالات فقط.
جرّب بنفسك
-- main.lua: نقطة الدخول - إنشاء طائر واستعراض قدراته
-- دالة مساعدة لتطبيق mixin على فئة (class)
local function applyMixin(class, mixin)
-- TODO: نسخ جميع الأساليب (methods) من mixin إلى الفئة
end
-- تطبيق Swimmable mixin على Bird قبل استدعاء Bird
-- ملاحظة: قد تحتاج إلى معالجة هذا في Bird.lua أو هنا
local Bird = require('Bird')
local Swimmable = require('Swimmable')
-- تطبيق mixin على فئة Bird
applyMixin(Bird, Swimmable)
-- قراءة المدخلات
local name = io.read()
local canSwimInput = io.read()
-- TODO: تحديد ما إذا كان الطائر يمكنه السباحة بناءً على المدخلات ("yes" أو "no")
-- TODO: إنشاء مثيل (instance) من Bird بالاسم والقدرة على السباحة
-- TODO: استدعاء eat() - موروثة من Animal
-- TODO: استدعاء chirp() - أسلوب خاص بـ Bird
-- TODO: استدعاء swim() فقط إذا كان الطائر يمكنه السباحة
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 مقابل الوراثةملخص - تجميع الروبوت