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. In metamethods, "cls" refers to the current table.

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 May 19, 2015 4:47 am GMT (diff)