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

استخدام 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) الحقيقي.

challenge icon

التحدي

سهل

لنقم ببناء فئة 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)

ستتلقى مدخلاً واحداً:

  1. القيمة السرية المراد تخزينها (على سبيل المثال، 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>")
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming