التغليف باستخدام الإغلاقات
أخفِ الحالة الخاصة باستخدام أنماط الكائنات المعتمدة على الإغلاقات
التغليف باستخدام الإغلاقات درس مجاني في Lua Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Lua Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Lua Academy 4 دروس في المجموع.
كائنات قائمة على الإغلاقات
بديل للبرمجة كائنية التوجه باستخدام جداول البيانات الوصفية: استخدم إغلاقًا لإخفاء الحالة. يكون الكائن جدولًا من الدوال؛ وتلتقط جميع الدوال المتغيرات الخاصة المحيطة بها. ولا حاجة إلى جدول بيانات وصفية. حالة خاصة فعلًا لا يمكن الوصول إليها من الخارج.
local function makeCounter(start)
local count = start or 0 -- truly private
return {
increment = function() count=count+1 end,
decrement = function() count=count-1 end,
get = function() return count end,
reset = function() count=0 end,
}
end
local c = makeCounter(10)
c.increment(); c.increment()
print(c.get()) -- 12
print(c.count) -- nil (truly private!)الخاص مقابل العام
مع الإغلاقات، يكون الفرق بين الخاص والعام مطلقًا: تشترك الإغلاقات في القيمة المحيطة (الخاصة)، ولا ترى الشفرة الخارجية إلا الجدول المُعاد (العام). ولا توجد طريقة للوصول إلى المتغيرات الخاصة دون المرور بواجهة البرمجة العامة.
local function makeAccount(initial)
local balance = initial
local transactions = {}
local function record(type, amount)
transactions[#transactions+1] = {type=type,amount=amount}
end
return {
deposit = function(n) record("D",n); balance=balance+n end,
withdraw = function(n)
if n>balance then error("insufficient funds") end
record("W",n); balance=balance-n
end,
balance = function() return balance end,
history = function() return transactions end,
}
endالإغلاقات المشتركة
تشترك الدوال المتعددة في الجدول المُعاد نفسه في المتغيرات المحيطة نفسها. وهذه هي طريقة مشاركة الحالة بين الأساليب دون كشفها للعامة. ترى جميع الأساليب المتغيرات الخاصة نفسها.
local function makeQueue()
local items = {} -- shared by all methods
local size = 0
return {
enqueue = function(v) size=size+1; items[size]=v end,
dequeue = function()
if size==0 then return nil end
local head=1
while items[head]==nil do head=head+1 end
local v=items[head]; items[head]=nil; return v
end,
isEmpty = function() return size==0 end,
count = function() return size end,
}
endالإغلاقات مقابل جداول البيانات الوصفية
الإغلاقات: حالة خاصة فعلًا، ولا تكلفة إضافية لجداول البيانات الوصفية، لكن لكل نسخة نسخها الخاصة من الدوال (استهلاك ذاكرة أكبر). جداول البيانات الوصفية: أساليب مشتركة (استهلاك ذاكرة أقل)، ويمكن فحصها باستخدام أدوات التصحيح، وتدعم التوارث. اختر بناءً على احتياجات الخصوصية وعدد النسخ.
-- Closure: per-instance functions (more memory)
local function makePoint(x,y)
return {
x = function() return x end,
y = function() return y end,
distance = function()
return math.sqrt(x*x+y*y)
end
}
end
-- Metatable: shared methods (less memory)
-- local Point = {}; Point.__index = Point
-- function Point.new(x,y) return setmetatable({x=x,y=y},Point) endتعديل الإغلاقات
تتيح الإغلاقات التي تحيط بمتغيرات قابلة للتعديل إنشاء كائنات ذات حالة دون استخدام جداول على الإطلاق. ويمكن لإغلاق واحد الاحتفاظ بالحالة وحساب النتائج.
local function makeAccumulator()
local sum, count = 0, 0
return {
add = function(n) sum=sum+n; count=count+1 end,
mean = function()
return count>0 and sum/count or 0
end,
total = function() return sum end,
n = function() return count end,
}
end
local acc = makeAccumulator()
for _, v in ipairs({10,20,30,40,50}) do
acc.add(v)
end
print(acc.total(),acc.mean(),acc.n()) -- 150 30.0 5المُنشئ مع التحقق
تتحقق المُنشئات القائمة على الإغلاقات من المدخلات في دالة المصنع، لا في أسلوب. وتؤدي المدخلات غير الصالحة إلى إعادة المصنع للقيمة nil أو لخطأ قبل إنشاء أي كائن.
local function makeRange(lo, hi)
assert(lo<=hi,"lo must be <= hi")
return {
contains = function(v) return v>=lo and v<=hi end,
clamp = function(v) return math.max(lo,math.min(hi,v)) end,
lo = function() return lo end,
hi = function() return hi end,
span = function() return hi-lo end,
}
end
local r = makeRange(0,100)
print(r.contains(50)) -- true
print(r.clamp(150)) -- 100الأحداث مع الإغلاقات
باثّ أحداث منفذ باستخدام الإغلاقات: تكون قائمة المعالجات خاصة؛ أما add/remove/emit فهي واجهة البرمجة العامة.
local function makeEmitter()
local handlers = {}
return {
on = function(event, fn)
handlers[event] = handlers[event] or {}
handlers[event][#handlers[event]+1] = fn
end,
emit = function(event, ...)
for _, fn in ipairs(handlers[event] or {}) do
fn(...)
end
end,
off = function(event) handlers[event] = {} end,
}
end
local emitter = makeEmitter()
emitter.on("data", function(v) print("Got:", v) end)
emitter.emit("data", 42) -- Got: 42الحفظ المؤقت باستخدام الإغلاق
غلاف memoize باستخدام الإغلاقات: تكون ذاكرة التخزين المؤقت خاصة، والدالة المغلّفة خاصة، ولا تُعاد إلا النسخة التي تستخدم الحفظ المؤقت.
local function memoize(fn)
local cache = {}
return function(...)
local key = table.concat({...}, ",")
if cache[key] == nil then
cache[key] = fn(...)
end
return cache[key]
end
end
local expensiveFib
expensiveFib = memoize(function(n)
if n<=1 then return n end
return expensiveFib(n-1)+expensiveFib(n-2)
end)
for i=0,10 do io.write(expensiveFib(i).." ") end
print()توسيع كائنات الإغلاقات
يمكنك توسيع كائن قائم على إغلاق بإنشاء كائن جديد يستدعي أساليب الكائن الأصلي. وهذا يشبه التغليف، لا التوارث — إذ يُفوَّض إلى الكائن الأصلي.
local function makeTimedCounter(start)
local base = makeCounter and makeCounter(start) or (function()
local c=start or 0
return {inc=function()c=c+1 end,get=function()return c end}
end)()
local callCount = 0
return {
increment = function()
callCount=callCount+1
base.inc()
end,
get = base.get,
calls = function() return callCount end,
}
endمتى تستخدم الإغلاقات
استخدم البرمجة كائنية التوجه القائمة على الإغلاقات عندما تحتاج إلى حالة خاصة فعلًا يجب تأمينها، أو عندما يكون عدد النسخ قليلًا (ولا تمثل الذاكرة مصدر قلق)، أو عندما تكتب شفرة بأسلوب وظيفي. واستخدم جداول البيانات الوصفية عندما تحتاج إلى التوارث، أو إلى عدد كبير من النسخ، أو إلى التكامل مع مكتبات البرمجة كائنية التوجه.
-- Guideline summary:
-- Closures: true privacy, no metatable, self-contained
-- Metatables: shared methods, inheritance, instanceof checks
-- Rule of thumb:
-- < 100 instances with private state -> closures
-- > 100 instances OR need inheritance -> metatables
print("Both patterns are valid Lua OOP.")تحقق سريع
ما الميزة الرئيسية للكائنات القائمة على الإغلاقات مقارنةً بالكائنات القائمة على جداول البيانات الوصفية؟
مراجعة: الإغلاقات للتغليف
الملخص:
- نمط الإغلاق: تعيد دالة المصنع جدولًا من الأساليب؛ وتكون الحالة متغيرًا محيطًا
- خصوصية حقيقية: لا توجد طريقة للوصول إلى المتغيرات المحيطة خارجيًا
- تشترك جميع الأساليب المُعادة في المتغيرات المحيطة الخاصة نفسها
- المقايضة: ذاكرة أكبر لكل نسخة مقابل خصوصية أفضل
- استخدمه في الشفرة الحساسة أمنيًا أو التصاميم ذات الأسلوب الوظيفي
الأسئلة الشائعة
هل درس «التغليف باستخدام الإغلاقات» مجاني؟
نعم — نص درس «التغليف باستخدام الإغلاقات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Lua Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Lua Academy 4 دروس في المجموع.
ماذا ستتعلم في «التغليف باستخدام الإغلاقات»؟
أخفِ الحالة الخاصة باستخدام أنماط الكائنات المعتمدة على الإغلاقات تتمرن على Lua Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Lua Academy؟
لا تُشترط خبرة سابقة. Lua Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «التغليف باستخدام الإغلاقات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Lua Academy هذا؟
نعم. كل درس في Lua Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الفئات عبر جداول البيانات الوصفية
- المنشئات وnew()
- أساليب النسخ وself
- التغليف باستخدام الإغلاقات