Lua Object Oriented

Con questo articolo tocchiamo il culmine di Lua. Scopriremo come implementare il paradigma ad oggetti in un linguaggio non Object Oriented come Lua. Scopriremo quindi come questo paradigma manuale lasci una personalizzazione enorme nel comportamento degli oggetti e nella definizione delle classi.

La prima cosa che ci viene in mente per simulare un oggetto è la seguente:

Contatore1 = { accumulatore = 0 }

function Contatore1.incrementa()
    Contatore1.accumulatore = Contatore1.accumulatore + 1
end

Questo modo è però sbagliato. Se ad esempio facciamo due oggetti

A = Contatore1
B = Contatore1

A.incrementa() -- Incrementa anche B!

Le azioni e le modifiche su di un oggetto si ripercuotono anche su tutti gli altri! Questo viola il principio per cui oggetti diversi hanno storie differenti. Grosso difetto!

Tentiamo di rimediare scegliendo un approccio più complesso.

Contatore2 = { accumulatore = 0 }

function Contatore2.incrementa(self)
    self.accumulatore = self.accumulatore + 1
end

Abbiamo aggiunto il parametro self. Questo ci permette di applicare l’effetto di un azione direttamente all’istanza sul quale è invocato. Possiamo inoltre rimuovere la necessità di specificare ogni volta il self usando l’operatore : (due-punti) sia nella definizione di un metodo che nella sua invocazione.

Contatore3 = { accumulatore = 0 }

function Contatore3:incrementa()
    self.accumulatore = self.accumulatore +1
end

Il nostro oggetto non è ancora completo. Vogliamo introdurre il concetto di “classe”, ovvero di un template grazie al quale creare oggetti con caratteristiche e comportamenti simili.

Possiamo simulare questo meccanismo introducendo un metodo costruttore.

function Contatore3:new (o)
  o = o or {}   -- Crea un oggetto se non ne viene passato nessuno.
  setmetatable(o, self) -- Meccanismo per trasferire le chiamate al "template" Contatore 3
  self.__index = self
  return o
end

Come potete vedere sfruttiamo il meccanismo delle meta-tabelle per generalizzare l’oggetto. Impostando a self il valore della metatabella di o (il nuovo oggetto) non facciamo altro che dire che: se il metodo (o la variabile) invocata su o non esiste in o allora cercala in self. Ora possiamo quindi evitare di ridefinire un metodo per ogni oggetto: ci penserà Lua ad andare a ripescare nella classe l’implementazione giusta.

Questa procedura permette di ottenere l’ereditarietà delle classi in modo terribilmente semplice.

ContatoreDoppio = Contatore3:new()
function ContatoreDoppio:incrementa()
    self.accumulatore = self.accumulatore + 2 -- Incrementa di DUE.
end

c = ContatoreDoppio:new() -- Istanza di contatore doppio.

Anzi, permette perfino di poter ridefinire metodi della singola istanza!

c = Contatore3:new() -- Crea una nuova istanza di contatore.
function c:incrementa()
    self.accumulatore = self.accumulatore + 2 -- Incrementa di DUE.
end

Il codice precedente mostra come è possibile ridefinire il metodo incrementa per la sola istanza c! Questa cosa è difficile da trovare persino per i linguaggi massivamente Object Oriented!

Insomma, con una manciata di righe di codice possiamo creare il nostro personale paradigma ad oggetti in Lua. Ci manca da vedere come poter creare metodi o variabili private. Ma questa è un altra storia.

Comments are closed.