Object Orientation Tutorial

lua-users home
wiki

Lua is not really an object-oriented language, and it doesn't have a built-in concept of classes. But it is easily possible to create your own class system using tables and metatables.

Simple metatable-based class

local MyClass = {} -- the table representing the class, which will double as the metatable for the instances

MyClass.__index = MyClass -- failed table lookups on the instances should fallback to the class table, to get methods



-- syntax equivalent to "MyClass.new = function..."

function MyClass.new(init)

  local self = setmetatable({}, MyClass)

  self.value = init

  return self

end



function MyClass.set_value(self, newval)

  self.value = newval

end



function MyClass.get_value(self)

  return self.value

end



local i = MyClass.new(5)

-- tbl:name(arg) is a shortcut for tbl.name(tbl, arg), except tbl is evaluated only once

print(i:get_value()) --> 5

i:set_value(6)

print(i:get_value()) --> 6

First we create a table to represent the class and contain its methods. We also make it double as the metatable for instances, but you can use a separate instance metatable if you like.

In the constructor, we create the instance (an empty table), give it the metatable, fill in fields, and return the new instance.

In the methods, we use a "self" parameter to get the instance to operate on. This is so common that Lua offers the : syntax sugar that calls a function entry from a table and inserts the table itself before the first arg.

There are some improvements that can be made:

local MyClass = {}

MyClass.__index = MyClass



setmetatable(MyClass, {

  __call = function (cls, ...)

    return cls.new(...)

  end,

})



function MyClass.new(init)

  local self = setmetatable({}, MyClass)

  self.value = init

  return self

end



-- the : syntax here causes a "self" arg to be implicitly added before any other args

function MyClass:set_value(newval)

  self.value = newval

end



function MyClass:get_value()

  return self.value

end



local instance = MyClass(5)

-- do stuff with instance...

Here we add a metatable to the class table that has the __call metamethod, which is triggered when a value is called like a function. We make it call the class's constructor, so you don't need the .new when creating instances. Another option would be to put the constructor right in the metamethod.

Also, to complement the : method call shortcut, Lua lets you use : when defining a function in a table, which implicitly adds a self argument so you don't have to type it out yourself.

Inheritance

It's easy to extend the design of the class in the above example to use inheritance:

local BaseClass = {}

BaseClass.__index = BaseClass



setmetatable(BaseClass, {

  __call = function (cls, ...)

    local self = setmetatable({}, cls)

    self:_init(...)

    return self

  end,

})



function BaseClass:_init(init)

  self.value = init

end



function BaseClass:set_value(newval)

  self.value = newval

end



function BaseClass:get_value()

  return self.value

end



---



local DerivedClass = {}

DerivedClass.__index = DerivedClass



setmetatable(DerivedClass, {

  __index = BaseClass, -- this is what makes the inheritance work

  __call = function (cls, ...)

    local self = setmetatable({}, cls)

    self:_init(...)

    return self

  end,

})



function DerivedClass:_init(init1, init2)

  BaseClass._init(self, init1) -- call the base class constructor

  self.value2 = init2

end



function DerivedClass:get_value()

  return self.value + self.value2

end



local i = DerivedClass(1, 2)

print(i:get_value()) --> 3

i:set_value(3)

print(i:get_value()) --> 5

Here we have the derived class table an __index metamethod that makes it inherit the base class. Also we moved the creating of the instance into the __call metamethods, and turned the constructors purely into initialization methods. This is so that the derived class can call the base class initialization function on itself.

One final optimization that can be done is to copy the contents of the base class into the derived class instead of using __index. This avoids the long __index chain that can slow down method calls, and also makes it so that if the base class has methods like __add, they will work like proper metamethods on the derived class. This is because __index is not followed when looking for metamethods:

local DerivedClass = {}

for k, v in pairs(BaseClass) do

  DerivedClass[k] = v

end

DerivedClass.__index = DerivedClass

Class creation function

Knowing all this, it's possible to create a convenience function that creates classes, optionally inheriting from other classes. Here is an example of such a function:

function (...)

  -- "cls" is the new class

  local cls, bases = {}, {...}

  -- copy base class contents into the new class

  for i, base in ipairs(bases) do

    for k, v in pairs(base) do

      cls[k] = v

    end

  end

  -- set the class's __index, and start filling an "is_a" table that contains this class and all of its bases

  -- so you can do an "instance of" check using my_instance.is_a[MyClass]

  cls.__index, cls.is_a = cls, {[cls] = true}

  for i, base in ipairs(bases) do

    for c in pairs(base.is_a) do

      cls.is_a[c] = true

    end

    cls.is_a[base] = true

  end

  -- the class's __call metamethod

  setmetatable(cls, {__call = function (c, ...)

    local instance = setmetatable({}, c)

    -- run the init method if it's there

    local init = instance._init

    if init then init(instance, ...) end

    return instance

  end})

  -- return the new class table, that's ready to fill with methods

  return cls

end

Closure-based objects

It's also possible to make objects using closures. Instances are slower to create and use more memory, but there are also some advantages (like faster instance field access), and it's an interesting example of how closures can be used.

local function MyClass(init)

  -- the new instance

  local self = {

    -- public fields go in the instance table

    public_field = 0

  }



  -- private fields are implemented using locals

  -- they are faster than table access, and are truly private, so the code that uses your class can't get them

  local private_field = init



  function self.foo()

    return self.public_field + private_field

  end



  function self.bar()

    private_field = private_field + 1

  end



  -- return the instance

  return self

end



local i = MyClass(5)

print(i.foo()) --> 5

i.public_field = 3

i.bar()

print(i.foo()) --> 9

Notice that the . syntax was used to call methods, not :. This is because the self variable is already stored in the methods as an upvalue, so it doesn't need to be passed in by the code calling it.

Inheritance is also possible this way:

local function BaseClass(init)

  local self = {}



  local private_field = init



  function self.foo()

    return private_field

  end



  function self.bar()

    private_field = private_field + 1

  end



  -- return the instance

  return self

end



local function DerivedClass(init, init2)

  local self = BaseClass(init)



  self.public_field = init2



  -- this is independent from the base class's private field that has the same name

  local private_field = init2



  -- save the base version of foo for use in the derived version

  local base_foo = self.foo

  function self.foo()

    return private_field + self.public_field + base_foo()

  end



  -- return the instance

  return self

end



local i = DerivedClass(1, 2)

print(i.foo()) --> 5

i.bar()

print(i.foo()) --> 6

Table- vs. Closure-based classes

Advantages of table-based:

Advantages of closure-based:

See Also


RecentChanges · preferences
edit · history
Last edited August 1, 2014 9:50 am GMT (diff)