الوصول عبر Closures
جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 45 من 70.
المتغيرات المحلية الخاصة عديمة الفائدة إذا لم تتمكن أبدًا من التفاعل معها. الحل هو تعريف دوال الجلب (getter) والتعيين (setter) داخل المنشئ (constructor)—حيث يكون لديها صلاحية الوصول إلى نطاق الإغلاق (closure's scope).
بما أن هذه الدوال يتم إنشاؤها داخل :new()، فبإمكانها "رؤية" المتغيرات المحلية. أنت تقوم بتعيينها مباشرة للكائن:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- متغير خاص
local obj = {}
-- تعريف الدوال داخل المنشئ
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endالآن يمكن الوصول إلى المتغير الخاص count—ولكن فقط من خلال الأساليب التي توفرها:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (لا يزال مخفياً!)لاحظ أن :getCount() و :increment() مُعرفة على obj مباشرة، وليس على جدول الفئة (class table). تحصل كل نسخة (instance) على نسختها الخاصة من هذه الدوال، وكل منها لديه وصول إلى الـ count الخاص بها. هذه هي المقايضة: الخصوصية الحقيقية تكلف ذاكرة إضافية قليلاً لأن الطرق (methods) لا يتم مشاركتها عبر النموذج الأولي (prototype).
التحدي
سهللنقم ببناء فئة Temperature تستخدم الخصوصية القائمة على الإغلاق (closure-based privacy) لتخزين قيمة درجة الحرارة بالدرجة المئوية (Celsius)، مع تحديد دوال الجلب (getter) والتعيين (setter) داخل المنشئ (constructor) لتوفير وصول محكم!
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
Temperature.lua: قم بإنشاء فئة حيث يتم تخزين قيمة درجة الحرارة في متغير محلي داخل المنشئ—مخفي تماماً عن الوصول الخارجي. يجب أن يقوم المنشئ:new(initialCelsius)بتخزين درجة الحرارة في متغير محلي، ثم تحديد هذه الدوال مباشرة على مثيل الكائن (object instance)::getCelsius()— يعيد درجة الحرارة الحالية بالدرجة المئوية.:setCelsius(value)— يقوم بتحديث درجة الحرارة إلى قيمة جديدة.:getFahrenheit()— يعيد درجة الحرارة محولة إلى فهرنهايت باستخدام الصيغة:(celsius * 9/5) + 32
main.lua: قم باستدعاء وحدة Temperature الخاصة بك واقرأ قيمتين من الإدخال: درجة حرارة أولية ودرجة حرارة جديدة لتعيينها. أنشئ مثيلاً من Temperature بالقيمة الأولية، واطبع قراءات Celsius و Fahrenheit، ثم استخدم دالة التعيين لتغيير درجة الحرارة واطبع كلتا القراءتين مرة أخرى.
ستتلقى مدخلين:
- درجة الحرارة الأولية بالدرجة المئوية (رقم)
- درجة حرارة جديدة لتعيينها (رقم)
يجب أن يتكون مخرجك من أربعة أسطر تعرض قراءات درجة الحرارة قبل وبعد التغيير:
Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}على سبيل المثال، إذا كانت المدخلات هي 0 و 100، يجب أن يكون المخرج:
Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212تذكر: يجب تخزين قيمة درجة الحرارة في متغير محلي داخل :new()، وليس على self. يتم تعريف دوال الجلب والتعيين داخل المنشئ حيث يمكنها الوصول إلى هذا المتغير الخاص من خلال الإغلاق (closure). محاولة الوصول إلى temp.celsius أو temp._celsius مباشرة يجب أن تعيد nil!
ورقة مرجعية
لإنشاء متغيرات خاصة (private) حقاً في فئات Lua، قم بتعريف دوال الجلب (getter) والتعيين (setter) داخل المنشئ (constructor) حيث يكون لديها وصول إلى المتغيرات المحلية من خلال الإغلاق (closure).
يتم تعيين الدوال مباشرة لمثيل الكائن (وليس لجدول الفئة)، مما يمنح كل مثيل نسخته الخاصة مع إمكانية الوصول إلى متغيراته الخاصة:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- متغير خاص
local obj = {}
-- تعريف الدوال داخل المنشئ
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endالاستخدام:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (لا يزال مخفياً!)المقايضة: يحصل كل مثيل على نسخته الخاصة من الدوال (لا يتم مشاركتها عبر النموذج الأولي)، مما يستهلك ذاكرة أكبر ولكنه يوفر خصوصية حقيقية.
جرّب بنفسك
-- استيراد وحدة Temperature
local Temperature = require('Temperature')
-- قراءة قيم المدخلات
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())
-- TODO: إنشاء مثيل (instance) لدرجة الحرارة بالقيمة الأولية
-- TODO: طباعة قراءات السيلزيوس والفهرنهايت الأولية
-- TODO: استخدام دالة التعيين (setter) لتغيير درجة الحرارة إلى newTemp
-- TODO: طباعة قراءات السيلزيوس والفهرنهايت المحدثة
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 للابنالمشترك مقابل الفريدملخص - التسلسل الهرمي للأشكال