استخدام Closures للخصوصية
جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 44 من 70.
تشير اتفاقيات التسمية إلى القصد، لكنها لا تمنع الوصول فعلياً. لا يزال بإمكان أي شخص كتابة player._health = 999 وسيعمل ذلك. لتحقيق الخصوصية الحقيقية، تقدم Lua نهجاً مختلفاً: تخزين البيانات في متغيرات محلية داخل المنشئ بدلاً من self.
الفكرة الأساسية هي أن المتغيرات المحلية داخل دالة ما لا يمكن الوصول إليها إلا داخل تلك الدالة - وأي دوال معرفة بداخلها. وهذا ما يسمى بـ closure:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- خاص حقاً! ليس على self.
local obj = {}
setmetatable(obj, Counter)
return obj
endفي هذا المثال، count موجود فقط داخل الدالة :new(). لا يتم تخزينه في obj، لذا لا توجد طريقة للوصول إليه من الخارج—myCounter.count تُرجع nil، ولا يوجد حقل مسبوق بشرطة سفلية لتجاوزه.
المتغير count يعيش في نطاق الإغلاق (closure). يستمر طالما أن الكائن موجود، لكنه مخفي تماماً عن الكود الخارجي. على عكس self._count، الذي هو مجرد اقتراح مهذب، فإن هذا النهج يجعل البيانات غير قابلة للوصول إليها حقاً.
بالطبع، البيانات المخفية ليست مفيدة إذا لم تتمكن أبداً من التفاعل معها. في الدرس القادم، ستتعلم كيفية تعريف الأساليب (methods) داخل المنشئ (constructor) التي يمكنها قراءة وتعديل هذه المتغيرات الخاصة؛ مما يمنحك وصولاً متحكماً فيه مع الحفاظ على مبدأ التغليف (encapsulation) الحقيقي.
التحدي
سهللنقم ببناء فئة SecretKeeper توضح الخصوصية الحقيقية للبيانات باستخدام الإغلاقات (closures)! على عكس اتفاقية الشرطة السفلية (underscore) من الدرس السابق، فإن هذا النهج يجعل البيانات غير قابلة للوصول حقاً من خارج الكائن.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
SecretKeeper.lua: قم بإنشاء فئة حيث يتم تخزين السر في متغير محلي (local variable) داخل المنشئ (constructor) — وليس علىself. يجب أن يقوم المنشئ:new(secretValue)بتخزين السر في متغير محلي موجود فقط داخل الإغلاق. في الوقت الحالي، لن يكون لدى الكائن أي وسيلة للوصول إلى هذا السر (سيأتي ذلك في الدرس القادم!)، ولكن يجب عليك أيضاً تخزين حقل عامnameمضبوط على"Keeper"في المثيل (instance) حتى نتمكن من التحقق من عمل الكائن.main.lua: قم باستدعاء وحدة SecretKeeper الخاصة بك واقرأ قيمة سرية من المدخلات. أنشئ مثيلاً من SecretKeeper بهذا السر. ثم أثبت أن السر خاص حقاً عن طريق طباعة ثلاثة أشياء:- حقل
nameالخاص بـ keeper (يجب أن يطبعKeeper) - ماذا يحدث عندما تحاول الوصول إلى
keeper.secret(يجب أن يكونnil) - ماذا يحدث عندما تحاول الوصول إلى
keeper._secret(يجب أن يكون أيضاًnil)
- حقل
ستتلقى مدخلاً واحداً:
- القيمة السرية المراد تخزينها (على سبيل المثال،
TopSecret123)
يجب أن يكون مخرجك عبارة عن ثلاثة أسطر توضح أنه بينما يعمل الحقل العام، فإن السر مخفي تماماً:
Name: Keeper
secret: nil
_secret: nilسيكون المخرج هو نفسه بغض النظر عن السر الذي تمرره — لأنه لا توجد طريقة للوصول إليه! المتغير المحلي داخل المنشئ غير مرئي للعالم الخارجي. هذا يثبت أن الخصوصية القائمة على الإغلاق تختلف جوهرياً عن اتفاقية تسمية الشرطة السفلية.
على سبيل المثال، إذا كان الإدخال هو MyPassword، فيجب أن يظل المخرج:
Name: Keeper
secret: nil
_secret: nilورقة مرجعية
لإنشاء بيانات خاصة حقًا في Lua، قم بتخزينها في متغيرات محلية (local variables) داخل المنشئ (constructor) بدلاً من self. يستخدم هذا الإغلاق (closure)—حيث لا يمكن الوصول إلى المتغيرات المحلية إلا داخل الدالة التي تم تعريفها فيها وأي دوال أخرى يتم تعريفها بداخلها.
مثال على فئة (class) تحتوي على بيانات خاصة:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- خاصة حقًا! ليست في self.
local obj = {}
setmetatable(obj, Counter)
return obj
endفي هذا المثال، يوجد count فقط داخل الدالة :new(). لا يتم تخزينه في obj، لذا فإن myCounter.count ستعيد nil. لا توجد طريقة للوصول إليه من الخارج—على عكس self._count (والذي هو مجرد اصطلاح تسمية)، فإن هذا النهج يجعل البيانات غير قابلة للوصول إليها بشكل حقيقي.
يستمر المتغير طالما أن الكائن موجود، ولكنه يظل مخفيًا تمامًا عن الكود الخارجي، مما يوفر تغليفًا (encapsulation) حقيقيًا.
جرّب بنفسك
-- استدعاء وحدة SecretKeeper
local SecretKeeper = require('SecretKeeper')
-- قراءة القيمة السرية من المدخلات
local secretValue = io.read()
-- TODO: إنشاء مثيل (instance) من SecretKeeper بالقيمة السرية
-- TODO: طباعة حقل الاسم الخاص بـ keeper (التنسيق: "Name: <name>")
-- TODO: طباعة ما يحدث عند الوصول إلى keeper.secret (التنسيق: "secret: <value>")
-- TODO: طباعة ما يحدث عند الوصول إلى keeper._secret (التنسيق: "_secret: <value>")
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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 للابنالمشترك مقابل الفريدملخص - التسلسل الهرمي للأشكال