Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

class

lua

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
like image 858
MatthewMartin Avatar asked Jul 07 '09 14:07

MatthewMartin


People also ask

Can you create classes in Lua?

Lua does not have the concept of class; each object defines its own behavior and has a shape of its own. Nevertheless, it is not difficult to emulate classes in Lua, following the lead from prototype-based languages, such as Self and NewtonScript. In those languages, objects have no classes.

Does Lua support OOP?

OOP in LuaYou can implement object orientation in Lua with the help of tables and first class functions of Lua. By placing functions and related data into a table, an object is formed.

What is __ index in Lua?

The __index metamethod in Lua Programming We can explicitly put in the __index method in a table and provide it the named values that we want it to return instead of nil.

What is a constructor in Lua?

Constructors are expressions that create and initialize tables. They are a distinctive feature of Lua and one of its most useful and versatile mechanisms. The simplest constructor is the empty constructor, {} , which creates an empty table; we saw it before.


1 Answers

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.

like image 98
2 revs Avatar answered Jan 06 '23 15:01

2 revs