Lua Academy · Lektion

Indkapsling med closures

Skjul privat tilstand ved hjælp af objektmønstre baseret på closures.

Lektion 4 af 412 trin

Indkapsling med closures er en gratis Lua Academy-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Lua Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Lua Academy-kurset indeholder 4 lektioner i alt.

Closure-baserede objekter

Et alternativ til OOP med metatabeller: Brug en closure til at skjule tilstanden. Objektet er en tabel med funktioner, og alle funktionerne lukker over private variabler. Der kræves ingen metatabel. Det giver virkelig privat tilstand, som ikke kan nås udefra.

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!)

Privat kontra offentlig

Med closures er forskellen mellem privat og offentlig absolut: Closures deler upvalue-værdien (privat), mens ekstern kode kun kan se den returnerede tabel (offentlig). Der er ingen måde at få adgang til private variabler på uden at gå gennem den offentlige 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

Delte closures

Flere funktioner i den samme returnerede tabel deler de samme upvalue-værdier. Sådan deles tilstand mellem metoder uden at eksponere den offentligt. Alle metoder ser de samme private variabler.

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

Closures kontra metatabeller

Closures: virkelig privat tilstand, ingen overhead fra metatabeller, men hver instans har sine egne kopier af funktionerne (mere hukommelse). Metatabeller: delte metoder (mindre hukommelse), kan inspiceres med fejlfindingsværktøjer og understøtter nedarvning. Vælg ud fra behovet for privatliv og antallet af instanser.

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

Ændring af closures

Closures over variabler, der kan ændres, lader dig oprette tilstandsfulde objekter uden tabeller overhovedet. En enkelt closure kan indeholde tilstand og beregne resultater.

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

Konstruktør med validering

Closure-baserede konstruktører validerer i fabriksfunktionen, ikke i en metode. Ugyldigt input får fabrikken til at returnere nil eller en fejl, før der oprettes noget objekt.

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

Hændelser med closures

En hændelsesudsender implementeret med closures: Listen over behandlere er privat, mens add/remove/emit er den offentlige 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

Memoisering med closures

En memoize-wrapper, der bruger closures: Cachen er privat, den indpakkede funktion er privat, og kun den memoisede version returneres.

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()

Udvidelse af closure-objekter

Du kan udvide et closure-baseret objekt ved at oprette et nyt, der kalder det oprindelige objekts metoder. Det minder om indpakning, ikke nedarvning — der delegeres til det oprindelige objekt.

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

Hvornår skal du bruge closures

Brug closure-baseret OOP, når du har brug for virkelig privat tilstand, der skal være sikker, når du har få instanser (hukommelse ikke er en bekymring), eller når du skriver kode i en funktionel stil. Brug metatabeller, når du har brug for nedarvning, mange instanser eller integration med OOP-biblioteker.

-- 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.")

Hurtigt tjek

Hvad er den største fordel ved closure-baserede objekter sammenlignet med metatabel-baserede objekter?

Opsummering: Closures til indkapsling

Opsummering:

  • Closure-mønster: En fabriksfunktion returnerer en metodetabel; tilstanden er en upvalue
  • Virkelig privat: Der er ingen måde at få adgang til upvalue-værdier udefra
  • Alle returnerede metoder deler de samme private upvalue-værdier
  • Afvejning: Mere hukommelse pr. instans mod bedre privatliv
  • Brug det til sikkerhedskritisk kode eller design i funktionel stil
Gratis at komme i gang

Lær Lua med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
40
Lektioner
159

Ofte stillede spørgsmål

Er lektionen “Indkapsling med closures” gratis?

Ja — alle 3 lektioner i læringssporet Lua Academy, inklusive “Indkapsling med closures”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Lua Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Indkapsling med closures”?

Skjul privat tilstand ved hjælp af objektmønstre baseret på closures. Du øver dig i Lua Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Lua Academy?

Der kræves ingen tidligere erfaring. Lua Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Indkapsling med closures”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Lua Academy-lektion?

Ja. Alle Lua Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Klasser via metatabeller
  2. Konstruktører og new()
  3. Instansmetoder og self
  4. Indkapsling med closures
← Tilbage til Lua Academy