Duck Typing
جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 39 من 70.
لا تهتم لغة Lua بفئة الكائن (class)—بل تهتم فقط بما يمكن للكائن فعله. يُطلق على هذا المفهوم اسم duck typing: "إذا كان يمشي مثل البطة ويصيح مثل البطة، فهو بطة". من الناحية البرمجية، إذا كان الكائن يمتلك الدالة (method) التي تستدعيها، فإنه سيعمل.
هذا يعني أنه يمكن استخدام أصناف (classes) غير مرتبطة تماماً معاً، طالما أنها تشترك في نفس اسم الطريقة (method name):
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endهذه الأصناف ليس بينها علاقة وراثة—فهي مستقلة تماماً. ومع ذلك، يمكننا التعامل معها بشكل متماثل:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- الناتج:
-- رسم دائرة
-- رسم مربع
-- رسم دائرةلا تتحقق الحلقة من نوع كل كائن. إنها ببساطة تستدعي :draw() وتثق في وجود هذه الطريقة. هذه المرونة قوية—يمكنك إضافة أنواع أشكال جديدة دون تعديل كود الحلقة على الإطلاق. طالما أن الفئة الجديدة تنفذ :draw()، فإنها ستعمل ببساطة.
التحدي
سهلدعونا نستعرض قوة "duck typing" من خلال بناء نظام إشعارات بفئات (classes) غير مرتبطة تماماً ببعضها البعض ولكن يمكن معالجتها جميعاً بنفس الطريقة!
ستقوم بإنشاء ثلاث فئات إشعارات مستقلة—لا ترث أي منها من الأخرى—ولكنها جميعاً تنفذ طريقة :send(). هذا يعني أنه يمكنك المرور عبر مجموعة مختلطة واستدعاء :send() على كل واحدة منها دون الاهتمام بنوعها الفعلي.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Email.lua: فئة تحتوي على منشئ:new(recipient)يقوم بتخزين الجهة التي سيُرسل إليها البريد الإلكتروني. يجب أن تقوم طريقة:send()الخاصة بها بطباعةEmailing {recipient}.SMS.lua: فئة منفصلة تماماً مع منشئ:new(phoneNumber). يجب أن تقوم طريقة:send()الخاصة بها بطباعةTexting {phoneNumber}.PushNotification.lua: فئة مستقلة أخرى مع منشئ:new(deviceId). يجب أن تقوم طريقة:send()الخاصة بها بطباعةPushing to {deviceId}.main.lua: اجمع كل شيء معاً! اقرأ ثلاثة مدخلات، وأنشئ نسخة واحدة من كل نوع إشعار، وضعها جميعاً في جدول واحد، ثم قم بالمرور عبرها واستدعاء:send()على كل منها.
ستتلقى ثلاثة مدخلات:
- مستلم بريد إلكتروني (مثلاً،
alice@example.com) - رقم هاتف (مثلاً،
555-1234) - معرف جهاز (مثلاً،
device_99)
في ملفك الرئيسي، أنشئ جميع كائنات الإشعارات الثلاثة، وقم بتخزينها في جدول، وكرر العملية عبر الجدول مستدعياً :send() على كل منها. يجب أن يكون الترتيب: البريد الإلكتروني أولاً، ثم الرسائل القصيرة (SMS)، ثم إشعارات الدفع (PushNotification).
على سبيل المثال، إذا كانت المدخلات هي bob@mail.com و 555-9876 و phone_42، يجب أن يكون المخرج:
Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42لاحظ كيف أن الحلقة التكرارية الخاصة بك لا تحتاج إلى التحقق من نوع كل كائن—فهي تثق فقط في أن كل كائن في المجموعة لديه طريقة :send(). هذا هو "duck typing" في أبهى صوره! إذا كان بإمكانه تنفيذ :send()، فهو إشعار.
ورقة مرجعية
تستخدم لغة Lua **كتابة البطة (duck typing)**: إذا كان الكائن يحتوي على الطريقة التي تستدعيها، فإنه يعمل، بغض النظر عن فئته (class). المبدأ هو "إذا كان يمشي مثل البطة ويصيح مثل البطة، فهو بطة".
يمكن استخدام فئات (classes) غير مرتبطة تماماً بشكل متبادل طالما أنها تشترك في نفس اسم الطريقة:
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endلا توجد علاقة وراثة بين هذه الفئات، ومع ذلك يمكن معاملتها بشكل متطابق:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Output:
-- Drawing a circle
-- Drawing a square
-- Drawing a circleلا تتحقق الحلقة (loop) من أنواع الكائنات - فهي ببساطة تستدعي الطريقة وتثق في وجودها. يمكنك إضافة أنواع جديدة دون تعديل الكود الحالي، طالما أنها تنفذ الطريقة المطلوبة.
جرّب بنفسك
-- main.lua
-- تجميع كافة أنواع الإشعارات معاً وتوضيح مفهوم الـ duck typing
-- استيراد فئات الإشعارات
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')
-- قراءة المدخلات
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()
-- TODO: إنشاء نسخة واحدة من كل نوع من أنواع الإشعارات
-- TODO: وضع جميع الإشعارات في جدول واحد (البريد الإلكتروني أولاً، ثم الرسائل النصية، ثم إشعارات الدفع)
-- TODO: المرور عبر الجدول واستدعاء ()send: لكل إشعار
-- يوضح هذا مفهوم الـ duck typing - نحن لا نتحقق من النوع، بل نقوم فقط باستدعاء ()send:
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 للابنالمشترك مقابل الفريدملخص - التسلسل الهرمي للأشكال