Hermetyzacja za pomocą domknięć
Proszę ukrywać prywatny stan za pomocą wzorców obiektowych opartych na domknięciach.
Hermetyzacja za pomocą domknięć to bezpłatna lekcja Lua Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Lua Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Lua Academy zawiera 4 lekcji w sumie.
Obiekty oparte na domknięciach
Alternatywą dla obiektowości opartej na metatabelach jest użycie domknięcia do ukrycia stanu. Obiekt jest tabelą funkcji, a wszystkie funkcje zamykają się nad prywatnymi zmiennymi. Metatabela nie jest potrzebna. Stan jest naprawdę prywatny i niedostępny z zewnątrz.
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!)Prywatne a publiczne
W przypadku domknięć rozróżnienie między elementami prywatnymi i publicznymi jest absolutne: domknięcia współdzielą upvalue (element prywatny), a kod zewnętrzny widzi tylko zwróconą tabelę (element publiczny). Nie ma możliwości uzyskania dostępu do prywatnych zmiennych inaczej niż przez publiczne API.
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,
}
endWspółdzielone domknięcia
Wiele funkcji w tej samej zwróconej tabeli współdzieli te same upvalue. W ten sposób stan jest współdzielony między metodami bez publicznego ujawniania. Wszystkie metody widzą te same prywatne zmienne.
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,
}
endDomknięcia a metatabele
Domknięcia: naprawdę prywatny stan, brak narzutu metatabel, ale każda instancja ma własne kopie funkcji (większe zużycie pamięci). Metatabele: współdzielone metody (mniejsze zużycie pamięci), możliwość inspekcji za pomocą narzędzi debugujących i obsługa dziedziczenia. Należy wybrać rozwiązanie zależnie od wymagań dotyczących prywatności i liczby instancji.
-- 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) endModyfikowanie stanu w domknięciach
Domknięcia nad zmiennymi, które można modyfikować, pozwalają tworzyć obiekty zachowujące stan nawet bez użycia tabel. Pojedyncze domknięcie może przechowywać stan i obliczać wyniki.
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 5Konstruktor z walidacją
Konstruktory oparte na domknięciach sprawdzają poprawność danych w funkcji fabrykującej, a nie w metodzie. Nieprawidłowe dane powodują, że fabryka zwraca nil lub błąd, zanim powstanie jakikolwiek obiekt.
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)) -- 100Zdarzenia z użyciem domknięć
Emiter zdarzeń zaimplementowany za pomocą domknięć: lista handlerów jest prywatna, a add/remove/emit stanowią publiczne API.
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: 42Memoizacja za pomocą domknięcia
Wrapper memoizujący wykorzystujący domknięcia: pamięć podręczna i opakowana funkcja są prywatne, a zwracana jest tylko wersja z memoizacją.
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()Rozszerzanie obiektów opartych na domknięciach
Obiekt oparty na domknięciu można rozszerzyć, tworząc nowy obiekt, który wywołuje metody oryginalnego obiektu. Przypomina to opakowywanie, a nie dziedziczenie — odwołania do oryginalnego obiektu są delegowane.
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,
}
endKiedy używać domknięć
Obiektowość opartą na domknięciach należy stosować, gdy: potrzebny jest naprawdę prywatny stan, który musi być chroniony; istnieje niewiele instancji (zużycie pamięci nie stanowi problemu); lub tworzony jest kod w stylu funkcyjnym. Metatabele należy stosować, gdy potrzebne jest dziedziczenie, występuje wiele instancji lub wymagana jest integracja z bibliotekami obiektowymi.
-- 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.")Szybkie sprawdzenie
Jaka jest główna zaleta obiektów opartych na domknięciach w porównaniu z obiektami opartymi na metatabelach?
Podsumowanie: domknięcia do enkapsulacji
Podsumowanie:
- Wzorzec domknięcia: funkcja fabrykująca zwraca tabelę metod, a stan jest przechowywany w upvalue
- Naprawdę prywatny stan: brak możliwości zewnętrznego dostępu do upvalue
- Wszystkie zwrócone metody współdzielą te same prywatne upvalue
- Kompromis: większe zużycie pamięci na instancję w zamian za lepszą prywatność
- Należy używać tego rozwiązania w kodzie krytycznym pod względem bezpieczeństwa lub projektowanym w stylu funkcyjnym
Często zadawane pytania
Czy lekcja „Hermetyzacja za pomocą domknięć” jest bezpłatna?
Tak — pełny tekst „Hermetyzacja za pomocą domknięć” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Lua Academy, przejdź na CoddyKit PRO. Kurs Lua Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Hermetyzacja za pomocą domknięć”?
Proszę ukrywać prywatny stan za pomocą wzorców obiektowych opartych na domknięciach. Ćwiczysz Lua Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Lua Academy?
Nie wymagamy żadnego doświadczenia. Lua Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Hermetyzacja za pomocą domknięć”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Lua Academy?
Tak. Każda lekcja Lua Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Klasy za pomocą metatabel
- Konstruktory i new()
- Metody instancji i self
- Hermetyzacja za pomocą domknięć