2009-07-07 3 views
6

Ich habe eine harte Zeit grokking Klassen in Lua. Fruitless googling führte mich zu Ideen über Meta-Tabellen und implizierte, dass Bibliotheken von Drittanbietern notwendig sind, um Klassen zu simulieren/schreiben.Wie erstellt man eine Klasse, Unterklasse und Eigenschaften in Lua?

Hier ist eine Probe (nur weil ich bemerkt habe ich bessere Antworten bekommen, wenn ich Beispielcode zur Verfügung stellen):

public class ElectronicDevice 
{ 
    protected bool _isOn; 
    public bool IsOn { get { return _isOn; } set { _isOn = value; } } 
    public void Reboot(){_isOn = false; ResetHardware();_isOn = true; } 
} 

public class Router : ElectronicDevice 
{ 
} 

public class Modem :ElectronicDevice 
{ 
    public void WarDialNeighborhood(string areaCode) 
    { 
     ElectronicDevice cisco = new Router(); 
     cisco.Reboot(); 
     Reboot(); 
     if (_isOn) 
      StartDialing(areaCode); 
    } 
} 

Hier ist mein erster Versuch die von Javier oben vorgeschlagen mit der Technik zu übersetzen.

Ich nahm den Rat von RBerteig. Allerdings Anrufungen auf abgeleitete Klassen ergeben noch: "attempt to call method 'methodName' (a nil value)"

--Everything is a table 
ElectronicDevice = {}; 

--Magic happens 
mt = {__index=ElectronicDevice}; 

--This must be a constructor 
function ElectronicDeviceFactory() 
    -- Seems that the metatable holds the fields 
    return setmetatable ({isOn=true}, mt) 
end 

-- Simulate properties with get/set functions 
function ElectronicDevice:getIsOn() return self.isOn end 
function ElectronicDevice:setIsOn(value) self.isOn = value end 
function ElectronicDevice:Reboot() self.isOn = false; 
    self:ResetHardware(); self.isOn = true; end 
function ElectronicDevice:ResetHardware() print('resetting hardware...') end 

Router = {}; 
mt_for_router = {__index=Router} 

--Router inherits from ElectronicDevice 
Router = setmetatable({},{__index=ElectronicDevice}); 

--Constructor for subclass, not sure if metatable is supposed to be different 
function RouterFactory() 
    return setmetatable ({},mt_for_router) 
end 

Modem ={}; 
mt_for_modem = {__index=Modem} 

--Modem inherits from ElectronicDevice 
Modem = setmetatable({},{__index=ElectronicDevice}); 

--Constructor for subclass, not sure if metatable is supposed to be different 
function ModemFactory() 
    return setmetatable ({},mt_for_modem) 
end 

function Modem:WarDialNeighborhood(areaCode) 
     cisco = RouterFactory(); 
     --polymorphism 
     cisco.Reboot(); --Call reboot on a router 
     self.Reboot(); --Call reboot on a modem 
     if (self.isOn) then self:StartDialing(areaCode) end; 
end 

function Modem:StartDialing(areaCode) 
    print('now dialing all numbers in ' .. areaCode); 
end 

testDevice = ElectronicDeviceFactory(); 
print("The device is on? " .. (testDevice:getIsOn() and "yes" or "no")); 
testDevice:Reboot(); --Ok 

testRouter = RouterFactory(); 
testRouter:ResetHardware(); -- nil value 

testModem = ModemFactory(); 
testModem:StartDialing('123'); -- nil value 
+0

Haben Sie [this] (http://lua-users.org/wiki/SimpleLuaClasses) gelesen? – ThibThib

+0

Ja und ich fand es undurchsichtig. – MatthewMartin

Antwort

8

Hier ist ein Beispiel wörtliche Wiedergabe des Codes, mit einer hilfreichen Class Bibliothek, die auf eine andere Datei verschoben werden könnte.

Dies ist keineswegs eine kanonische Umsetzung Class bedeutet; Fühlen Sie sich frei, Ihr Objektmodell zu definieren, wie Sie es möchten.

Class = {} 

function Class:new(super) 
    local class, metatable, properties = {}, {}, {} 
    class.metatable = metatable 
    class.properties = properties 

    function metatable:__index(key) 
     local prop = properties[key] 
     if prop then 
      return prop.get(self) 
     elseif class[key] ~= nil then 
      return class[key] 
     elseif super then 
      return super.metatable.__index(self, key) 
     else 
      return nil 
     end 
    end 

    function metatable:__newindex(key, value) 
     local prop = properties[key] 
     if prop then 
      return prop.set(self, value) 
     elseif super then 
      return super.metatable.__newindex(self, key, value) 
     else 
      rawset(self, key, value) 
     end 
    end 

    function class:new(...) 
     local obj = setmetatable({}, self.metatable) 
     if obj.__new then 
      obj:__new(...) 
     end 
     return obj 
    end 

    return class 
end 

ElectronicDevice = Class:new() 

function ElectronicDevice:__new() 
    self.isOn = false 
end 

ElectronicDevice.properties.isOn = {} 
function ElectronicDevice.properties.isOn:get() 
    return self._isOn 
end 
function ElectronicDevice.properties.isOn:set(value) 
    self._isOn = value 
end 

function ElectronicDevice:Reboot() 
    self._isOn = false 
    self:ResetHardware() 
    self._isOn = true 
end 

Router = Class:new(ElectronicDevice) 

Modem = Class:new(ElectronicDevice) 

function Modem:WarDialNeighborhood(areaCode) 
    local cisco = Router:new() 
    cisco:Reboot() 
    self:Reboot() 
    if self._isOn then 
     self:StartDialing(areaCode) 
    end 
end 

Wenn Sie Objekte zum get/set-Methoden zu halten, würden Sie __index und __newindex Funktionen nicht benötigen, und konnten nur einen __index Tisch. In diesem Fall auf die einfachste Art und Weise Vererbung zu simulieren so etwas wie diese:

BaseClass = {} 
BaseClass.index = {} 
BaseClass.metatable = {__index = BaseClass.index} 

DerivedClass = {} 
DerivedClass.index = setmetatable({}, {__index = BaseClass.index}) 
DerivedClass.metatable = {__index = DerivedClass.index} 

Mit anderen Worten, die __index Tabelle der abgeleiteten Klasse „erbt“ die __index Tabelle der Basisklasse.Dies funktioniert, weil Lua beim Delegieren an eine Tabelle __index das Nachschlagen effektiv wiederholt, sodass die Metamethoden der Tabelle __index aufgerufen werden.

Seien Sie auch vorsichtig beim Anruf obj.Method(...) vs obj:Method(...). obj:Method(...) ist syntaktischer Zucker für obj.Method(obj, ...), und das Mischen der beiden Aufrufe kann zu ungewöhnlichen Fehlern führen.

6

Es gibt eine Reihe von Möglichkeiten, können Sie es tun, aber das ist, wie ich (mit einem Schuss auf Vererbung aktualisiert):

function newRGB(r, g, b) 
    local rgb={ 
     red = r; 
     green = g; 
     blue = b; 
     setRed = function(self, r) 
      self.red = r; 
     end; 
     setGreen = function(self, g) 
      self.green= g; 
     end; 
     setBlue = function(self, b) 
      self.blue= b; 
     end; 
     show = function(self) 
      print("red=",self.red," blue=",self.blue," green=",self.green); 
     end; 
    } 
    return rgb; 
end 

purple = newRGB(128, 0, 128); 
purple:show(); 
purple:setRed(180); 
purple:show(); 

---// Does this count as inheritance? 
function newNamedRGB(name, r, g, b) 
    local nrgb = newRGB(r, g, b); 
    nrgb.__index = nrgb; ---// who is self? 
    nrgb.setName = function(self, n) 
     self.name = n; 
    end; 
    nrgb.show = function(self) 
     print(name,": red=",self.red," blue=",self.blue," green=",self.green); 
    end; 
    return nrgb; 
end 

orange = newNamedRGB("orange", 180, 180, 0); 
orange:show(); 
orange:setGreen(128); 
orange:show(); 

I Implementieren Sie nicht privat, geschützt usw. although it is possible.

1

Es ist wirklich einfach, Klassen-wie OOP in Lua zu machen; setzen Sie einfach alle ‚Methoden‘ in der __index Feld eines Metatabelle:

local myClassMethods = {} 
local my_mt = {__index=myClassMethods} 

function myClassMethods:func1 (x, y) 
    -- Do anything 
    self.x = x + y 
    self.y = y - x 
end 

............ 

function myClass() 
    return setmetatable ({x=0,y=0}, my_mt) 

Ich persönlich habe noch nie Erbschaft benötigt, so dass die oben genug für mich ist. Wenn es nicht genug ist, können Sie eine Metatabelle für die Methoden Tabelle:

local mySubClassMethods = setmetatable ({}, {__index=myClassMethods}) 
local my_mt = {__index=mySubClassMethods} 

function mySubClassMethods:func2 (....) 
    -- Whatever 
end 

function mySubClass() 
    return setmetatable ({....}, my_mt) 

Update: Es gibt einen Fehler in der aktualisierten Code:

Router = {}; 
mt_for_router = {__index=Router} 
--Router inherits from ElectronicDevice 
Router = setmetatable({},{__index=ElectronicDevice}); 

Beachten Sie, dass Router initialisieren und bauen mt_for_router von diesem; aber dann weisen Sie Router einer neuen Tabelle zu, während mt_for_router immer noch auf das Original Router verweist.

Ersetzen Sie die Router={} durch die Router = setmetatable({},{__index=ElectronicDevice}) (vor der mt_for_router Initialisierung).

3

Die Art und Weise, wie ich es wollte, war durch die Implementierung einer clone() -Funktion.
Beachten Sie, dass dies für Lua 5.0 ist. Ich denke, 5.1 hat mehr eingebaute objektorientierte Konstruktionen.

clone = function(object, ...) 
    local ret = {} 

    -- clone base class 
    if type(object)=="table" then 
      for k,v in pairs(object) do 
        if type(v) == "table" then 
          v = clone(v) 
        end 
        -- don't clone functions, just inherit them 
        if type(v) ~= "function" then 
          -- mix in other objects. 
          ret[k] = v 
        end 
      end 
    end 
    -- set metatable to object 
    setmetatable(ret, { __index = object }) 

    -- mix in tables 
    for _,class in ipairs(arg) do 
      for k,v in pairs(class) do 
        if type(v) == "table" then 
          v = clone(v) 
        end 
        -- mix in v. 
        ret[k] = v 
      end 
    end 

    return ret 
end 

Sie dann eine Klasse als Tabelle definieren:

Thing = { 
    a = 1, 
    b = 2, 
    foo = function(self, x) 
    print("total = ", self.a + self.b + x) 
    end 
} 

es zu instanziieren oder daraus zu ziehen, verwenden Sie clone() und man kann die Dinge außer Kraft setzen, indem sie in einer anderen Tabelle vorbei (oder

myThing:foo(100); 
: Tabellen) als Mix-In

myThing = clone(Thing, { a = 5, b = 10 }) 

, können Sie die Syntax verwenden zu nennen,

Das wird Druck:

total = 115 

eine Unterklasse abzuleiten, Sie im Grunde einen weiteren Prototyp-Objekt definieren:

BigThing = clone(Thing, { 
    -- and override stuff. 
    foo = function(self, x) 
     print("hello"); 
    end 
} 

Diese Methode ist sehr einfach, vielleicht zu einfach, aber es funktionierte gut für Mein Projekt.

1

Ihre aktualisierte Code ist wordy, sollte aber funktionieren. Außer, haben Sie einen Tippfehler, der eine der Metatables bricht:

 
--Modem inherits from ElectronicDevice 
Modem = setmetatable({},{__index,ElectronicDevice}); 

 
--Modem inherits from ElectronicDevice 
Modem = setmetatable({},{__index=ElectronicDevice}); 

Das bestehende Fragment null lesen sollte die Modem gemacht MetaTable ein Array sein, wo das erste Element mit ziemlicher Sicherheit war (der üblicher Wert von _G.__index es sei denn, Sie strict.lua oder etwas ähnliches verwenden) und das zweite Element ist ElectronicDevice.

Die Lua Wiki Beschreibung wird Sinn machen, nachdem Sie Metatables ein bisschen mehr grokked haben. Eine Sache, die hilft, ist es, eine kleine Infrastruktur zu bauen, um die üblichen Muster einfacher zu machen.

Ich würde auch empfehlen, das Kapitel über OOP in PiL lesen. Sie werden auch die Kapitel zu Tabellen und Metatabellen noch einmal lesen müssen. Außerdem habe ich mit der Online-Version der 1. Ausgabe verlinkt, aber eine Kopie der 2. Ausgabe ist sehr zu empfehlen. Es gibt auch ein paar Artikel in der Lua Gems Buch, die sich beziehen. Es wird auch empfohlen.

4

Wenn Sie das Rad nicht neu erfinden wollen, gibt es eine schöne Lua-Bibliothek, die mehrere Objektmodelle implementiert. Es heißt .

1

Eine weitere einfache Ansatz für Unterklasse

local super = require("your base class") 
local newclass = setmetatable({}, {__index = super }) 
local newclass_mt = { __index = newclass } 

function newclass.new(...) -- constructor 
    local self = super.new(...) 
    return setmetatable(self, newclass_mt) 
end 

Sie können immer noch die Funktionen von Super verwenden, selbst wenn überschrieben

function newclass:dostuff(...) 
    super.dostuff(self,...) 
    -- more code here -- 
end 

vergessen Sie nicht, einen Punkt zu verwenden, wenn der Selbst auf die übergeordnete Funktion übergeben