0Pricing
Lua Academy · Lekcja

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,
  }
end

Współ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,
  }
end

Domknię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) end

Modyfikowanie 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  5

Konstruktor 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))     -- 100

Zdarzenia 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: 42

Memoizacja 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,
  }
end

Kiedy 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

  1. Klasy za pomocą metatabel
  2. Konstruktory i new()
  3. Metody instancji i self
  4. Hermetyzacja za pomocą domknięć
← Powrót do Lua Academy