Object-Oriented Programming in Lua
this article shows how to implement class-based oop in lua using its prototype-based system.
table - the data structure
first and foremost, always remember this famous quote from programming in lua:
tables in lua are not a data structure; they are the data structure.
therefore, a class is a table, an object is a table, and so is everything else.
metatable and metamethod
a table may have a metatable, which contains a group of functions, called
metamethods. metamethods can change the behavior of a table. for example,
you can add metamethod __add
to allow table addition.
in fact, the core of lua’s oop is the __index
metamethod, which has the
prototype:
function(table, field)
where table
is the table being accessed, and field
is the name of the field
being accessed.
when access an absent field in a table, the interpreter will look for the
__index
metamethod. if found, it is called and its result is returned.
otherwise, nil
is returned.
an example
this example shows the use of metamethod __index
:
obj1 = { val = 1024, }
obj2 = {}
print(obj2.val)
mt = {
__index = function(table, field)
return obj1[field]
end,
}
setmetatable(obj2, mt)
print(obj2.val)
the above code will output:
nil
1024
a shortcut
because the use of __index
metamethod is common, lua also provides a shortcut
for it: rather than being a function, the __index
field can also be a table.
in that case, the table is used to lookup the absent field. this is to say,
writing:
__index = mytable,
is equivalent as:
__index = function(table, field)
return mytable[field]
end
class-based oop in lua
with the above knowledge in mind, we can now implement class-based oop, despite
its prototype-based nature. the root of the class hierarchy is table object
:
object = {
new = function(self)
local obj = {}
local mt = copy(getmetatable(self)) or {}
mt.__index = self
setmetatable(obj, mt)
return obj
end,
super = function(self)
return getmetatable(self).__index
end,
}
-
the
new
method has two purposes:-
create a sub class.
-
create an instance.
-
-
the
super
method returns:-
the super class, for a class.
-
the class, for an instance.
-
the copy
function is merely a simple shallow copy:
copy = function(obj)
local obj2
if type(obj) == "table" then
obj2 = {}
for k, v in pairs(obj) do
obj2[k] = v
end
else
obj2 = obj
end
return obj2
end
use the class hierarchy
-
to create a derive class:
dev1 = object:new()
-
to create a chain of derive classes:
dev2 = dev1:new() dev3 = dev2:new()
-
to add a class method:
dev1.getval = function(self) print("function:dev1.getval:") return self.val end
-
to add a class field:
dev1.val = 1
-
to add an instance field:
dev1.new = function(self) local obj = dev1:super().new(self) obj.val = 11 return obj end
-
to add a metamethod:
mt = getmetatable(dev1) mt.__add = function(l, r) return l.val + r.val end setmetatable(dev1, mt)
-
to overwrite a class method:
dev2.getval = function(self) print("function:dev2.getval:") return self.val end
-
to overwrite a class field:
dev2.val = 2
-
to overwrite an instance field:
dev2.new = function(self) local obj = dev2:super().new(self) obj.val = 22 return obj end
-
to overwrite a metamethod:
mt = getmetatable(dev2) mt.__add = function(l, r) return (l.val + r.val) / 2 end setmetatable(dev1, mt)