How to create a class, subclass and properties in Lua?
class, lua
Solution
Here's an example literal transcription of your code, with a helpful `Class` library that could be moved to another file.
This is by no means a canonical implementation of `Class`; feel free to define your object model however 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 were to stick to get/set methods for properties, you wouldn't need `__index` and `__newindex` functions, and could just have an `__index` table. In that 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 derived class's `__index` table "inherits" the base class's `__index` table. This works because Lua, when delegating to an `__index` table, effectively repeats the lookup on it, so the `__index` table's metamethods are invoked.
Also, be wary about calling `obj.Method(...)` vs `obj:Method(...)`. `obj:Method(...)` is syntactic sugar for `obj.Method(obj, ...)`, and mixing up the two calls can produce unusual errors.
Problem
I'm having a hard time grokking classes in Lua. Fruitless googling led me to ideas about meta-tables, and implied that third-party libraries are necessary to simulate/write classes. Here's a sample (just because I've noticed I get better 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 the above using the technique suggested by Javier. I took the advice of RBerteig. However, invocations on 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 ```