- Offizieller Beitrag
Hier nun ein kleiner Exkurs zu Lua Objekten.
Der Begriff "Objekt" ist in Lua eigentlich fehl am Platze, da es keinen Speichertyp Objekt gibt. In Lua spielt sich alles in Tables (Arrays) ab. Sämtliche Funktionen sind in Tables hinterlegt und Objekte sind nur Tables mit Zusatzfunktion.
Hier mal ein ganz einfaches Objekt ( eine Eigenschaft, eine Methode )
local object1 = {
eigenschaft = nil, -- bei Erstellung hat Eigenschaft keinen Wert
methode = function(self, param1)
if self.eigenschaft ~= nil then -- Eigenschaft hat einen Wert
return param1 * self.eigenschaft -- Rückgabe Produkt Wert-Eigenschaft * Wert-Parameter
else -- erster Aufruf Methode, Eigenschaft noch ohne Wert
self.eigenschaft = param1 -- Wert-Parameter an Eigenschaft zuweisen und
return param1 -- diesen Wert zurückgeben
end
end
}
Alles anzeigen
Die Methode enthält als ersten Parameter das Schlüsselwort "self". Dies ist gewissermassen die Referenz auf den umschließenden Table-Konstruktor "{}", der der lokalen Variablen "object" zugewiesen wurde.
Somit ist innerhalb des Objektes (table object) ein Zugriff auf andere Mitglieder des Objektes (Eigenschaften und Methoden) möglich. Dazu wird das Schlüsselwort "self" genutzt.
Zugriff auf Eigenschaft, lesend: "wert = self.property", schreibend: "self.property = wert". Zugriff auf Methode: "retVal = self:Methode(param)" oder "retVal = self.Methode(self, param)".
Auf das erstellte Objekt läßt sich dann analog zugreifen. Nur wird statt "self" die Objektvariable verwendet. Diese kann ich aber vor Anwendung auch an eine andere Variable übergeben:
local o = object
Zugriff auf Eigenschaft, lesend: wert = o.property
schreibend: o.property = wert
Zugriff auf Methode: retVal = o:Methode(param) oder retVal = o.Methode(o, param)
Bitte beim Methodenaufruf die beiden möglichen Syntaxformen beachten. Entweder "Objekt-Doppelpunkt-Methode(Parameter)" oder "Objekt-Punkt-Methode(Objekt, Parameter)".
Wir können die Methoden unseres Objektes auch bequem in einem anderen Objekt anwenden. Dazu weisen wir dem anderen Objekt diese Methode einfach mit Bezug auf das Quellobjekt zu:
local object2 = {
eigenschaft = nil,
methode = object1.methode
}
local ret = object1:methode(12) print('Ergebnis', ret) ---> 12
ret = object1:methode(12) print('Ergebnis', ret) ---> 144
ret = object2:methode(12) print('Ergebnis', ret) ---> 12
ret = object2:methode(12) print('Ergebnis', ret) ---> 144
Alles anzeigen
Und hier ein kleines Anwendungsbeispiel.
Wir erstellen ein simples Taschenrechner-Objekt, das folgendes können soll:
- 4 Grundrechenarten (Übergabe von 2 Werten)
- Speichern eines bestimmten Wertes, Speicherabruf, Speicher löschen
- autom. Speichern letztes und aktuelles Ergebnis
- abrufbare Historie (gesamt od. bestimmter Schritt) der durchgeführten Rechnungen, Historie löschen
Spoiler anzeigen
local objCalculator = {
-- Properties
lastResult = nil,
currResult = nil,
memVal = nil,
history = {},
-- Methoden
-- Historie Verwaltung
addHistory = function(self, op, a, b, res) table.insert(self.history, {op, a, b, res}) end,
-- ohne Positionsabgabe wird die letzte Operation zurückgegeben in einem table {'operation', param1, param2, Ergebnis} und zweiter Rückgabewert ist die Positionsnummer
getHistory = function(self, pos) local max = #self.history if max == 0 then return nil, 0 end if pos == nil or pos > max then pos = max elseif pos < 1 then pos = 1 end return self.history[pos], pos end,
clearHistory = function(self) self.history = {} end,
-- Speicherverwaltung
setMem = function(self, v) self.memVal = v end,
getMem = function(self) return self.memVal end,
clearMem = function(self) self.memVal = nil end,
switchResults = function(self, result) self.lastResult = self.currResult self.currResult = result end,
-- Rechnen
add = function(self, a, b) local res = a+b self:switchResults(res) self:addHistory('add', a, b, res) return res end,
sub = function(self, a, b) local res = a-b self:switchResults(res) self:addHistory('sub', a, b, res) return res end,
mul = function(self, a, b) local res = a*b self:switchResults(res) self:addHistory('mul', a, b, res) return res end,
div = function(self, a, b) local res if b == nil then res = nil else res = a/b end self:switchResults(res) self:addHistory('div', a, b, res) return res end,
}
local Rechner = objCalculator
local Ergebnis
Ergebnis = Rechner:add(2,4) print('2+4',Ergebnis)
Ergebnis = Rechner:sub(12,3) print('12-3',Ergebnis)
Ergebnis = Rechner:mul(7,8) print('7*8',Ergebnis)
Ergebnis = Rechner:div(72,9) print('72/9',Ergebnis)
Rechner:setMem(Rechner.currResult) print('getMem',Rechner:getMem())
Rechner:clearMem() print('Speicher gelöscht',Rechner:getMem())
-- Auslesefunktion für die komplette Historie
local History = function()
local sHistorie, tCalc, iPos = ''
while iPos ~= 0 do
tCalc, iPos = Rechner:getHistory(iPos)
if not tCalc then
break
else
sHistorie = '['..iPos..'] '..table.concat(tCalc, '\t')..'\n'..sHistorie
iPos = iPos -1
end
end
if sHistorie ~= '' then
print(sHistorie)
else
print('keine Historie Daten')
end
end
print('Historie gesamt')
History()
print('Historie 3.te Rechenoperation')
local t = Rechner:getHistory(3)
print(unpack(t))
Rechner:clearHistory()
print('Historie gelöscht')
History()
Alles anzeigen
Das funktioniert alles, wie gewünscht.
Der nächste Schritt ist nun, eine Klasse zu erstellen.
Wozu Klassen?
Wenn ich mehrere Objekte erstellen möchte, mit demselben Verhalten (Accounts, User in einem Spiel etc.) definiere ich einmalig ein Objekt dieser Klasse. Und immer wenn ich ein neues identisches Objekt benötige, wird eine neue Instanz des Klassenobjektes erstellt.
Klassen im strengen Sinne gibt es in Lua nicht. Lua verwendet stattdessen das Prototyp-Konzept um Klassen zu emulieren. Dazu erstellen wir ein Objekt, das ausschließlich als Prototyp für seine Instanzen existiert.
In Lua wird das, wie folgt, umgesetzt:
In diesem Fall ist "b" das Klassenobjekt. Mit diesem Aufruf durchsucht "a" das Objekt "b" nach Eigenschaften und Methoden, die es selbst nicht hat. Die MetaMethode "__index" sorgt für die Vererbung.
Das wenden wir jetzt auf das erste Bsp. an und fügen dem Objekt die Methode "create" hinzu:
local objPrototyp = {
eigenschaft = nil,
methode = function(self, param1)
if self.eigenschaft ~= nil then
return param1 * self.eigenschaft
else
self.eigenschaft = param1
return param1
end
end,
create = function(self, obj)
obj = obj or {}
setmetatable(obj, self)
self.__index = self
return obj
end
}
local newObj = objPrototyp:create()
local ret = newObj:methode(12) print('Ergebnis', ret) ---> 12
ret = newObj:methode(12) print('Ergebnis', ret) ---> 144
Alles anzeigen
Nun wollen wir ein anderes Objekt erstellen und diesem Objekt die Eigenschaften und Methoden des Prototyps hinzufügen:
local objX = {
name = 'Test',
getName = function(self) return self.name end
}
print( objX:getName() ) ---> "Test"
objX = objPrototyp:create(objX)
ret = objX:methode(12) print('Ergebnis', ret) ---> 12
ret = objX:methode(12) print('Ergebnis', ret) ---> 144
Alles anzeigen
Jetzt testen, ob die alten Eigenschaften und Methoden noch zur Verfügung stehen
Und das funktioniert.
Ich kann also auf diese Weise nicht nur neue Objekte erstellen, sondern auch Objekte "mixen". Das ermöglicht gutes modulares Arbeiten.
Lua kennt in Objekten keine Unterscheidung zwischen privat und public. Das liegt in der Tabellenstruktur begründet.
Aber das ist kein Hindernis. Hier mal ein einfaches Bsp. eine Methode nur unter Bedingungen ausführen zu lassen:
local oMuster = {
security = 0, -- erlaubt nicht das Ausführen der Methode "methA"
val1 = nil,
val2 = nil,
methA = function(self, a, b) if self.security == 1 then self.val1 = a self.val2 = b return a*b else return nil end end,
create = function(self, obj)
obj = obj or {}
setmetatable(obj, self)
self.__index = self
return obj
end
}
local o1 = oMuster:create{security = 1} -- neues Objekt erstellen und gleichzeitig Eigenschaft "security" setzen
local o2 = oMuster:create() -- neues Objekt erstellen mit Standardwert für Eigenschaft "security"
print(o1:methA(2,6)) ---> 12
print(o2:methA(2,6)) ---> nil
Alles anzeigen