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:

1. create a sub class.

2. create an instance.

• the super method returns:

1. the super class, for a class.

2. 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


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)