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

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()، فإنها ستعمل ببساطة.

challenge icon

التحدي

سهل

دعونا نستعرض قوة "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() على كل منها.

ستتلقى ثلاثة مدخلات:

  1. مستلم بريد إلكتروني (مثلاً، alice@example.com)
  2. رقم هاتف (مثلاً، 555-1234)
  3. معرف جهاز (مثلاً، 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:
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming