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