Object Orientation Tutorial


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)

AltStyle によって変換されたページ (->オリジナル) /