How to create a class, subclass and properties in Lua?

I have difficult grokking classes in Lua . The barren googling led me to ideas about meta-tables and implied that third-party libraries are needed to simulate / write classes.

Here is an example (just because I noticed that I am getting the best answers when I provide sample code):

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); } } 

Here is my first attempt to translate this above using the method suggested by Javier.

I took the advice of RBerteig. However, calls to derived classes still yield: "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 
+6
class lua
source share
7 answers

Here's an example of literally transcribing your code with the useful Class library, which you can transfer to another file.

This is by no means a canonical implementation of Class ; Feel free to define your object model as you like.

 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 

If you must adhere to get / set methods for properties, you do not need the __index and __newindex and can only have a __index table. In this case, the easiest way to simulate inheritance is something like this:

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

In other words, the table of the __index derived class "inherits" the table of the __index base class. This works because Lua, when delegating the __index table __index effectively repeats the search on it, so the metathemes of the __index table are __index .

Also be careful when calling obj.Method(...) vs obj:Method(...) . obj:Method(...) is the syntax sugar for obj.Method(obj, ...) , and mixing the two calls can lead to unusual errors.

+8
source share

There are several ways to do this, but this is the way I do (updated with deletion on inheritance):

 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 do not implement private, protected, etc. although it is possible .

+6
source share

If you don't want to reinvent the wheel, there is a good Lua library that implements several object models. It was called LOOP .

+4
source share

The way I liked it was to implement the clone () function.
Please note that this is for Lua 5.0. I think 5.1 has more built-in object-oriented constructs.

 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 

Then you define the class as a table:

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

To create an instance or extract from it, you use clone (), and you can override it by passing them in another table (or tables) as mix-ins

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

The call uses the syntax:

 myThing:foo(100); 

This will print:

 total = 115 

To get a subclass, you basically define another prototype object:

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

This method is REALLY simple, perhaps too simple, but it worked well for my project.

+3
source share

It's very easy to make cool OOP in Lua; just put all the β€œmethods” in the __index field of the metatetable:

 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) 

Personally, I never needed inheritance, so for me it is enough. If this is not enough, you can set the meta for the method table:

 local mySubClassMethods = setmetatable ({}, {__index=myClassMethods}) local my_mt = {__index=mySubClassMethods} function mySubClassMethods:func2 (....) -- Whatever end function mySubClass () return setmetatable ({....}, my_mt) 

update: An error occurred in the updated code:

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

Note that you are initializing the Router and building mt_for_router ; but then you reassign Router to a new table, and mt_for_router still points to the original Router .

Replace Router={} with Router = setmetatable({},{__index=ElectronicDevice}) (before mt_for_router initialized).

+1
source share

Your updated code is verbose, but should work. Except , you have a typo that breaks one of the meta meta:

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

must read

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

The existing fragment made the Modem meta-furniture an array in which the first element was almost certainly nil (the usual value is _G.__index , unless you use strict.lua or something like that), and the second element is ElectronicDevice .

The Lua Wiki description will make sense after you have even more grotked metatables. One thing that helps is to create a small infrastructure to make regular patterns more understandable.

I would also recommend reading the chapter on OOP in PiL . You will also want to re-read chapters on tables and metathemes. In addition, I am associated with an online copy of the 1st edition, but it is highly recommended to have a copy of the second. There are also a couple of articles in Lua Gems that relate to it. It is also recommended.

+1
source share

Another easy approach for subclassing

 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 

You can still use functions from the superclass even if they are overwritten

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

don't forget to use ONE point when passing yourself to the superclass function

+1
source share

All Articles