التفويض (Delegation)
جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 50 من 70.
تخزين كائن داخل كائن آخر هو مجرد الخطوة الأولى. تكمن القوة الحقيقية للتركيب (composition) في التفويض (delegation)—عندما يقوم الكائن الخارجي بتمرير استدعاءات الميثود (method calls) إلى مكوناته الداخلية.
فكر في تشغيل سيارة. أنت لا تمد يدك تحت غطاء المحرك وتدير المحرك يدوياً. بدلاً من ذلك، تقوم بتدوير المفتاح، وتتولى السيارة الباقي داخلياً.
في الكود، يعني هذا أن فئة Car توفر طريقة :start() بسيطة تُفوض العمل الفعلي للمحرك:
function Car:start()
print("Turning the key...")
self.engine:run() -- تفويض للكائن الداخلي
endلا تحتاج السيارة إلى معرفة كيفية عمل المحرك—فهي تكتفي بإخبار المحرك بالقيام بعمله. هذا يحافظ على فصل المسؤوليات. تتولى الفئة Engine منطق المحرك، بينما تقوم Car بتنسيق الأجزاء:
function Engine:run()
print("Engine running at " .. self.horsepower .. " HP")
end
local engine = Engine:new(250)
local car = Car:new("Coupe", engine)
car:start()
-- المخرجات:
-- تدوير المفتاح...
-- المحرك يعمل بقوة 250 حصانيوفر التفويض واجهة نظيفة لمستخدمي الفئة (class) الخاصة بك. فهم يتفاعلون مع السيارة، وليس مع أجزائها الداخلية. إذا قمت لاحقاً بتبديل تنفيذ المحرك بآخر مختلف، فستظل الواجهة العامة للسيارة كما هي، ولن يتغير سوى التفويض الداخلي.
التحدي
سهللنقم ببناء نظام مشغل موسيقى يوضح مفهوم التفويض (Delegation)! ستقوم بإنشاء مكون Speaker و MusicPlayer يحتويه. لن يقوم المشغل بمعالجة إخراج الصوت مباشرة - بدلاً من ذلك، سيفوض هذه المسؤولية لمكبر الصوت الداخلي الخاص به.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
Speaker.lua: أنشئ فئة Speaker مع منشئ:new(volume)يقوم بتخزين مستوى الصوت. قم بتضمين طريقة:playSound(trackName)التي تطبع:Playing "{trackName}" at volume {volume}MusicPlayer.lua: أنشئ فئة MusicPlayer التي يقبل منشئها:new(brand, speaker)اسم العلامة التجارية وكائن Speaker. يقوم المشغل بتخزين مكبر الصوت بداخله. قم بتضمين هذه الطرق::play(trackName)— تطبع{brand} player starting...ثم تفوض المهمة لطريقة:playSound()الخاصة بمكبر الصوت.:stop()— تطبع{brand} player stopped
main.lua: اجمع كل شيء معاً! اقرأ المدخلات الخاصة بمستوى صوت مكبر الصوت، والعلامة التجارية للمشغل، واسم المسار. أنشئ Speaker أولاً، ثم مرره إلى منشئ MusicPlayer. استدعِ طريقة:play()الخاصة بالمشغل مع اسم المسار، ثم استدعِ:stop().
ستتلقى ثلاثة مدخلات:
- مستوى صوت مكبر الصوت (رقم)
- العلامة التجارية لمشغل الموسيقى (سلسلة نصية)
- اسم المسار المراد تشغيله (سلسلة نصية)
يجب أن يظهر مخرجك المشغل وهو ينسق مع مكبر الصوت الخاص به:
{brand} player starting...
Playing "{trackName}" at volume {volume}
{brand} player stoppedعلى سبيل المثال، إذا كانت المدخلات هي 75، و SoundMax، و Summer Vibes، فيجب أن يكون المخرج:
SoundMax player starting...
Playing "Summer Vibes" at volume 75
SoundMax player stoppedلاحظ كيف أن MusicPlayer لا يعرف كيفية إنتاج الصوت - فهو ببساطة يخبر مكبر الصوت الخاص به بمعالجة هذا الجزء. هذا هو التفويض في أبهى صوره: الكائن الخارجي يوفر واجهة نظيفة بينما يقوم المكون الداخلي بالعمل المتخصص!
ورقة مرجعية
يسمح التفويض (Delegation) للكائن الخارجي بتوجيه استدعاءات الدوال إلى مكوناته الداخلية، مما يحافظ على فصل المسؤوليات وإنشاء واجهات برمجية نظيفة.
يوفر الكائن الخارجي دوالاً بسيطة تفوض العمل إلى الكائنات الداخلية:
function Car:start()
print("Turning the key...")
self.engine:run() -- تفويض العمل للكائن الداخلي
endيتعامل الكائن الداخلي مع منطقه الخاص والمتخصص:
function Engine:run()
print("Engine running at " .. self.horsepower .. " HP")
end
local engine = Engine:new(250)
local car = Car:new("Coupe", engine)
car:start()
-- المخرجات:
-- Turning the key...
-- Engine running at 250 HPفوائد التفويض:
- لا تحتاج الفئة الخارجية إلى معرفة كيفية عمل الكائن الداخلي
- يتفاعل المستخدمون مع واجهة عامة نظيفة
- يمكن تبديل التنفيذات الداخلية دون تغيير الواجهة العامة
جرّب بنفسك
-- main.lua
-- اجمع كل شيء معاً!
local Speaker = require('Speaker')
local MusicPlayer = require('MusicPlayer')
-- قراءة المدخلات
local volume = tonumber(io.read())
local brand = io.read()
local trackName = io.read()
-- TODO: قم بإنشاء Speaker بمستوى الصوت volume المعطى
-- TODO: قم بإنشاء MusicPlayer باستخدام brand و speaker
-- TODO: استدعِ طريقة play الخاصة بالمشغل مع اسم المسار
-- TODO: استدعِ طريقة stop الخاصة بالمشغل
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 مقابل الوراثةملخص - تجميع الروبوت