0Pricing
Lua Academy · Leçon

Encapsulation avec des fermetures

Masquez l’état privé à l’aide de modèles d’objets fondés sur des fermetures.

Encapsulation avec des fermetures est une leçon Lua Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Lua Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Lua Academy comprend 4 leçons au total.

Objets fondés sur des fermetures

Une alternative à la POO fondée sur les métatables : utilisez une fermeture pour masquer l'état. L'objet est une table de fonctions ; toutes les fonctions capturent des variables privées. Aucune métatable n'est nécessaire. L'état est véritablement privé et inaccessible de l'extérieur.

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

Privé ou public

Avec les fermetures, la distinction entre privé et public est absolue : les fermetures partagent la valeur capturée (privée), tandis que le code externe ne voit que la table renvoyée (publique). Il est impossible d'accéder aux variables privées autrement qu'en passant par l'API publique.

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

Fermetures partagées

Plusieurs fonctions d'une même table renvoyée partagent les mêmes valeurs capturées. C'est ainsi que l'état est partagé entre les méthodes sans être exposé publiquement. Toutes les méthodes voient les mêmes variables privées.

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

Fermetures ou métatables

Fermetures : état véritablement privé, aucun surcoût de métatable, mais chaque instance possède ses propres copies des fonctions (davantage de mémoire). Métatables : méthodes partagées (moins de mémoire), possibilité d'inspection avec les outils de débogage et prise en charge de l'héritage. Choisissez selon vos besoins de confidentialité et le nombre d'instances.

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

Fermetures mutables

Les fermetures portant sur des variables modifiables permettent de créer des objets avec état sans utiliser de tables. Une seule fermeture peut conserver un état et calculer des résultats.

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

Constructeur avec validation

Les constructeurs fondés sur des fermetures valident les données dans la fonction de fabrique, et non dans une méthode. Des entrées invalides amènent la fabrique à renvoyer nil ou une erreur avant qu'un objet soit créé.

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

Événements avec des fermetures

Un émetteur d'événements implémenté avec des fermetures : la liste des gestionnaires est privée ; add/remove/emit constituent l'API publique.

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

Mémorisation par fermeture

Une enveloppe de mémorisation utilisant des fermetures : le cache est privé, la fonction enveloppée est privée et seule la version mémorisée est renvoyée.

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

Extension d'objets fondés sur des fermetures

Vous pouvez étendre un objet fondé sur une fermeture en en créant un nouveau qui appelle les méthodes de l'original. Cela ressemble à un enveloppement, et non à un héritage : la délégation se fait vers l'original.

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

Quand utiliser les fermetures

Utilisez la POO fondée sur les fermetures lorsque vous avez besoin d'un état véritablement privé qui doit être sécurisé, lorsque vous avez peu d'instances (la mémoire n'est pas un problème) ou lorsque vous écrivez du code de style fonctionnel. Utilisez les métatables lorsque vous avez besoin d'héritage, de nombreuses instances ou d'une intégration avec des bibliothèques de POO.

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

Vérification rapide

Quel est le principal avantage des objets fondés sur des fermetures par rapport aux objets fondés sur des métatables ?

Récapitulatif : fermetures et encapsulation

Résumé :

  • Modèle de fermeture : une fonction de fabrique renvoie une table de méthodes ; l'état est une valeur capturée
  • Confidentialité véritable : aucun accès externe aux valeurs capturées
  • Toutes les méthodes renvoyées partagent les mêmes valeurs capturées privées
  • Compromis : davantage de mémoire par instance, mais une meilleure confidentialité
  • À utiliser pour le code critique en matière de sécurité ou les conceptions de style fonctionnel

Questions Fréquemment Posées

La leçon « Encapsulation avec des fermetures » est-elle gratuite ?

Oui — le texte complet de « Encapsulation avec des fermetures » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Lua Academy, passe à CoddyKit PRO. Le cours Lua Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Encapsulation avec des fermetures » ?

Masquez l’état privé à l’aide de modèles d’objets fondés sur des fermetures. Tu pratiques Lua Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Lua Academy ?

Aucune expérience préalable n'est requise. Lua Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Encapsulation avec des fermetures » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Lua Academy ?

Oui. Chaque leçon Lua Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Classes via les métatables
  2. Constructeurs et new()
  3. Méthodes d’instance et self
  4. Encapsulation avec des fermetures
← Retour à Lua Academy