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

جداول للقراءة فقط

جزء من قسم Object Oriented Programming في رحلة Lua على Coddy — الدرس 46 من 70.

توفر الإغلاقات (Closures) خصوصية حقيقية، لكنها تتطلب تعريف الدوال (methods) داخل المنشئ (constructor). هناك نهج آخر لحالة استخدام مختلفة: جعل الكائن (object) للقراءة فقط بعد إنشائه. هذا مفيد عندما تريد أن تكون الحقول مرئية ولكن غير قابلة للتغيير—مثل إحداثيات نقطة ثابتة.

يقوم الميثود الخفي (metamethod) ‏__newindex باعتراض محاولات تعيين قيم جديدة لجدول. ومن خلال جعله يمنع جميع عمليات التعيين، يمكنك تجميد كائن:

local ImmutablePoint = {}

function ImmutablePoint.new(x, y)
    local data = {x = x, y = y}
    
    local proxy = {}
    setmetatable(proxy, {
        __index = data,
        __newindex = function()
            error("Cannot modify immutable object")
        end
    })
    
    return proxy
end

الحيلة تكمن في استخدام جدول وكيل (proxy table). البيانات الفعلية موجودة في data، بينما يتفاعل المستخدم مع الـ proxy الفارغ.

عند القراءة، يجلب __index من data. وعند الكتابة، يتم تفعيل __newindex بدلاً من ذلك—ونحن نقوم بحظره:

local p = ImmutablePoint.new(10, 20)
print(p.x)  -- 10 (القراءة تعمل)
p.x = 50    -- خطأ: لا يمكن تعديل كائن غير قابل للتغيير

هذا النمط مفيد لكائنات التكوين، أو الثوابت الرياضية، أو أي بيانات يجب ألا تتغير أبداً بعد التهيئة. يظل الكائن قابلاً للقراءة بالكامل، لكن لا يمكنك تعديله.

challenge icon

التحدي

سهل

لنقم ببناء فئة ImmutableConfig التي تنشئ كائنات إعدادات للقراءة فقط! بمجرد إنشائها، يجب أن تسمح كائنات الإعدادات هذه بقراءة إعداداتها ولكن تمنع تماماً أي محاولات لتعديلها.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • ImmutableConfig.lua: قم بإنشاء وحدة (module) تنتج كائنات إعدادات مجمدة. يجب أن تقوم وظيفة .new(appName, version, maxUsers) بتخزين هذه القيم الثلاث داخلياً، ثم إرجاع جدول وكيل (proxy table) يسمح بقراءة جميع الحقول ولكنه يمنع أي تعديلات. عندما يحاول شخص ما تغيير قيمة، يجب أن يطبع Error: Config is read-only بدلاً من إثارة خطأ فعلي.
  • main.lua: اجعل ImmutableConfig ينبض بالحياة! اقرأ ثلاثة مدخلات لقيم الإعدادات، وأنشئ كائن إعدادات، ثم أثبت أنه يعمل بشكل صحيح من خلال:
    • طباعة جميع القيم الأصلية الثلاث
    • محاولة تعديل حقل maxUsers (مما يجب أن يؤدي إلى ظهور رسالة الخطأ)
    • طباعة maxUsers مرة أخرى لإثبات أنها لم تتغير بالفعل

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

  1. اسم التطبيق (سلسلة نصية)
  2. الإصدار (سلسلة نصية)
  3. الحد الأقصى للمستخدمين (رقم)

يجب أن يظهر مخرجك قيم الإعدادات، والخطأ عند محاولة التعديل، والتأكيد على أن القيمة ظلت دون تغيير:

App: {appName}
Version: {version}
Max Users: {maxUsers}
Error: Config is read-only
Max Users: {maxUsers}

على سبيل المثال، إذا كانت المدخلات هي MyApp و 2.0 و 100، فيجب أن يكون المخرج:

App: MyApp
Version: 2.0
Max Users: 100
Error: Config is read-only
Max Users: 100

تذكر: يستخدم نمط الوكيل (proxy pattern) جدولاً فارغاً يتفاعل معه المستخدم، بينما تعيش البيانات الفعلية في جدول داخلي منفصل. تقوم الميتاميثود __index بجلب البيانات من جدول البيانات، وتقوم __newindex باعتراض (ومنع) أي محاولات كتابة!

ورقة مرجعية

الأسلوب الخفي (metamethod) المسمى __newindex يعترض محاولات تعيين قيم جديدة لجدول ما. من خلال حظر جميع التعيينات، يمكنك إنشاء كائنات غير قابلة للتغيير (للقراءة فقط):

local ImmutablePoint = {}

function ImmutablePoint.new(x, y)
    local data = {x = x, y = y}
    
    local proxy = {}
    setmetatable(proxy, {
        __index = data,
        __newindex = function()
            error("Cannot modify immutable object")
        end
    })
    
    return proxy
end

يستخدم **نمط الوكيل (proxy pattern)** جدولاً فارغاً (proxy) يتفاعل معه المستخدم، بينما تعيش البيانات الفعلية في جدول داخلي منفصل (data):

  • __index يجلب القيم من الجدول data عند القراءة
  • __newindex يعترض محاولات الكتابة ويحظرها
local p = ImmutablePoint.new(10, 20)
print(p.x)  -- 10 (القراءة تعمل)
p.x = 50    -- خطأ: لا يمكن تعديل كائن غير قابل للتغيير

هذا النمط مفيد لكائنات التكوين (configuration objects)، أو الثوابت الرياضية، أو أي بيانات يجب أن تظل دون تغيير بعد التهيئة.

جرّب بنفسك

-- استيراد وحدة ImmutableConfig
local ImmutableConfig = require('ImmutableConfig')

-- قراءة المدخلات
local appName = io.read()
local version = io.read()
local maxUsers = tonumber(io.read())

-- TODO: إنشاء كائن إعدادات باستخدام ImmutableConfig.new()

-- TODO: طباعة القيم الثلاث الأصلية بالتنسيق التالي:
-- App: {appName}
-- Version: {version}
-- Max Users: {maxUsers}

-- TODO: محاولة تعديل حقل maxUsers (يجب أن يؤدي هذا إلى ظهور رسالة خطأ)

-- TODO: طباعة maxUsers مرة أخرى لإثبات أنه لم يتغير
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming