00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Programmazione ad Oggetti in Lua

Lua non ha un sistema di classi integrato come Java o Python, ma offre tutti gli strumenti necessari per implementare la programmazione ad oggetti attraverso tabelle e metatabelle. Questo approccio, basato su prototipi, e’ estremamente flessibile e permette di creare gerarchie di classi complete.

Le Basi della OOP in Lua

In Lua, un oggetto e’ una tabella che contiene dati (attributi) e funzioni (metodi). Le metatabelle, in particolare il metamethod __index, permettono di implementare l’ereditarieta e il lookup dei metodi. Ogni “classe” e’ semplicemente una tabella che funge da prototipo per i suoi oggetti.

-- Un semplice "oggetto" come tabella
local persona = {
    nome = "Mario",
    eta = 30,
    saluta = function(self)
        print("Ciao, sono " .. self.nome)
    end
}

persona.saluta(persona)  -- Ciao, sono Mario

L’Operatore : (Due Punti)

L’operatore : e’ uno zucchero sintattico che passa automaticamente la tabella come primo parametro self. E’ fondamentale per la OOP in Lua perche rende la sintassi dei metodi molto piu’ leggibile.

local persona = {
    nome = "Mario",
    eta = 30
}

-- Definizione con due punti: self e' implicito
function persona:saluta()
    print("Ciao, sono " .. self.nome)
end

-- Equivalente a:
-- function persona.saluta(self)
--     print("Ciao, sono " .. self.nome)
-- end

-- Chiamata con due punti: la tabella viene passata come self
persona:saluta()  -- Ciao, sono Mario

-- Equivalente a:
persona.saluta(persona)

Creare Classi con Metatabelle

Il pattern standard per creare una classe in Lua utilizza una tabella come classe e __index per il lookup dei metodi. La funzione new funge da costruttore.

local Persona = {}
Persona.__index = Persona

function Persona.new(nome, eta)
    local self = setmetatable({}, Persona)
    self.nome = nome
    self.eta = eta
    return self
end

function Persona:saluta()
    print("Ciao, sono " .. self.nome .. " e ho " .. self.eta .. " anni")
end

function Persona:compieAnni()
    self.eta = self.eta + 1
    print(self.nome .. " ora ha " .. self.eta .. " anni")
end

-- Creazione di oggetti
local mario = Persona.new("Mario", 30)
local lucia = Persona.new("Lucia", 25)

mario:saluta()       -- Ciao, sono Mario e ho 30 anni
lucia:saluta()       -- Ciao, sono Lucia e ho 25 anni
mario:compieAnni()   -- Mario ora ha 31 anni

Come Funziona il Costruttore

Quando si chiama Persona.new(), succedono tre cose fondamentali:

  1. Viene creata una nuova tabella vuota {}.
  2. La metatabella viene impostata a Persona, che contiene __index = Persona.
  3. Quando si accede a un metodo (es. mario:saluta()), Lua non lo trova in mario, quindi cerca in Persona tramite __index.
-- Analisi passo per passo
local Cane = {}
Cane.__index = Cane

function Cane.new(nome)
    local self = setmetatable({}, Cane)
    -- self e' una tabella vuota con metatabella Cane
    self.nome = nome  -- aggiungiamo dati all'istanza
    return self
end

function Cane:abbaia()
    print(self.nome .. " dice: Bau!")
end

local fido = Cane.new("Fido")
-- fido = { nome = "Fido" } con metatabella Cane

-- fido:abbaia() -> Lua cerca "abbaia" in fido -> non c'e'
-- -> cerca in Cane (tramite __index) -> trovato!
fido:abbaia()  -- Fido dice: Bau!

Il Parametro self

Il parametro self rappresenta l’istanza corrente dell’oggetto. Quando si usa l’operatore :, self viene passato automaticamente. Attenzione a non confondere . e : nelle chiamate ai metodi.

local Contatore = {}
Contatore.__index = Contatore

function Contatore.new(inizio)
    return setmetatable({ valore = inizio or 0 }, Contatore)
end

function Contatore:incrementa(n)
    self.valore = self.valore + (n or 1)
end

function Contatore:getValore()
    return self.valore
end

local c = Contatore.new(10)
c:incrementa()       -- OK: self = c
c:incrementa(5)      -- OK: self = c
print(c:getValore()) -- 16

-- Errore comune: dimenticare i due punti
-- c.incrementa()    -- ERRORE: self sarebbe nil

Ereditarieta con __index

L’ereditarieta in Lua si implementa creando una catena di __index. La classe figlia imposta il suo __index alla classe genitore, permettendo il lookup dei metodi lungo la gerarchia.

-- Classe base
local Forma = {}
Forma.__index = Forma

function Forma.new(colore)
    local self = setmetatable({}, Forma)
    self.colore = colore or "nero"
    return self
end

function Forma:getColore()
    return self.colore
end

function Forma:descrivi()
    return "Forma di colore " .. self.colore
end

-- Classe derivata
local Cerchio = setmetatable({}, { __index = Forma })
Cerchio.__index = Cerchio

function Cerchio.new(raggio, colore)
    local self = Forma.new(colore)    -- chiama il costruttore padre
    setmetatable(self, Cerchio)       -- imposta la metatabella a Cerchio
    self.raggio = raggio
    return self
end

function Cerchio:area()
    return math.pi * self.raggio ^ 2
end

-- Override del metodo descrivi
function Cerchio:descrivi()
    return string.format("Cerchio di raggio %g, colore %s", self.raggio, self.colore)
end

local c = Cerchio.new(5, "rosso")
print(c:getColore())  -- rosso (ereditato da Forma)
print(c:area())       -- 78.539816339745
print(c:descrivi())   -- Cerchio di raggio 5, colore rosso

Esempio Completo: Gerarchia Animale

Ecco un esempio pratico che dimostra una gerarchia di classi completa con classe base, classi derivate, metodi ereditati e override.

-- Classe base: Animale
local Animale = {}
Animale.__index = Animale

function Animale.new(nome, verso)
    local self = setmetatable({}, Animale)
    self.nome = nome
    self.verso = verso or "..."
    self.energia = 100
    return self
end

function Animale:parla()
    print(self.nome .. " fa: " .. self.verso)
end

function Animale:mangia(cibo)
    self.energia = self.energia + 10
    print(self.nome .. " mangia " .. cibo .. " (energia: " .. self.energia .. ")")
end

function Animale:dormi()
    self.energia = self.energia + 20
    print(self.nome .. " sta dormendo... (energia: " .. self.energia .. ")")
end

function Animale:__tostring()
    return string.format("%s [energia: %d]", self.nome, self.energia)
end

-- Classe derivata: Cane
local Cane = setmetatable({}, { __index = Animale })
Cane.__index = Cane

function Cane.new(nome, razza)
    local self = Animale.new(nome, "Bau!")
    setmetatable(self, Cane)
    self.razza = razza or "Meticcio"
    return self
end

function Cane:riporta(oggetto)
    self.energia = self.energia - 15
    print(self.nome .. " riporta: " .. oggetto .. "!")
end

function Cane:__tostring()
    return string.format("Cane: %s (%s) [energia: %d]", self.nome, self.razza, self.energia)
end

-- Classe derivata: Gatto
local Gatto = setmetatable({}, { __index = Animale })
Gatto.__index = Gatto

function Gatto.new(nome, indoor)
    local self = Animale.new(nome, "Miao!")
    setmetatable(self, Gatto)
    self.indoor = indoor or false
    return self
end

function Gatto:faiLeFusa()
    print(self.nome .. " fa le fusa... purrrr")
end

-- Override del metodo mangia
function Gatto:mangia(cibo)
    if cibo == "pesce" then
        self.energia = self.energia + 20
        print(self.nome .. " adora il pesce! (energia: " .. self.energia .. ")")
    else
        Animale.mangia(self, cibo)  -- chiama il metodo del genitore
    end
end

function Gatto:__tostring()
    local tipo = self.indoor and "da appartamento" or "da esterno"
    return string.format("Gatto %s: %s [energia: %d]", tipo, self.nome, self.energia)
end

-- Utilizzo della gerarchia
local fido = Cane.new("Fido", "Labrador")
local micia = Gatto.new("Micia", true)

fido:parla()           -- Fido fa: Bau!
micia:parla()          -- Micia fa: Miao!

fido:mangia("osso")    -- Fido mangia osso (energia: 110)
micia:mangia("pesce")  -- Micia adora il pesce! (energia: 120)
micia:mangia("pollo")  -- Micia mangia pollo (energia: 130)

fido:riporta("palla")  -- Fido riporta: palla!
micia:faiLeFusa()      -- Micia fa le fusa... purrrr

fido:dormi()           -- Fido sta dormendo... (energia: 115)

print(tostring(fido))  -- Cane: Fido (Labrador) [energia: 115]
print(tostring(micia)) -- Gatto da appartamento: Micia [energia: 130]

Verifica del Tipo

Per verificare se un oggetto appartiene a una certa classe, si puo’ controllare la catena delle metatabelle.

local function isInstance(oggetto, classe)
    local mt = getmetatable(oggetto)
    while mt do
        if mt == classe then return true end
        local parent = getmetatable(mt)
        if parent then
            mt = parent.__index
        else
            mt = nil
        end
    end
    return false
end

print(isInstance(fido, Cane))     -- true
print(isInstance(fido, Animale))  -- true
print(isInstance(fido, Gatto))    -- false

Conclusione

La programmazione ad oggetti in Lua, pur non essendo integrata nel linguaggio come in Java o Python, offre una flessibilita straordinaria. Attraverso tabelle, metatabelle e il metamethod __index, e’ possibile implementare classi, ereditarieta, polimorfismo e incapsulamento. Questo approccio basato su prototipi permette di modellare qualsiasi struttura orientata agli oggetti necessaria per i propri progetti.