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:
- Viene creata una nuova tabella vuota
{}. - La metatabella viene impostata a
Persona, che contiene__index = Persona. - Quando si accede a un metodo (es.
mario:saluta()), Lua non lo trova inmario, quindi cerca inPersonatramite__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.