Using Inheritance (sort of)
When we actually need inheritance, things get a little more complicated. We need to use two of lua's slightly harder features to get it to work:
colon notation. A little background on these will help:
All "objects" in lua are tables, and tables can something called a metatable added to them. Metatables can have special methods on them which run under certain circumstances (called metamethods), such as keys being added. A full list of metamethods is available here.
The metamethod we are interested in is called called
__index, which gets triggered when a key is not found in the table.
There are two ways of using
__index. The first is to assign it a function, which gets passed two arguments:
key. This is useful if you want to provide a default value if a key in a table isn't found, which I use in the
spellData example in the previous post.
The other way of using
__index is to pass it another table of methods to call, like in this example:
actual, we provide
actual with all the methods on
meta. A table can only have one meta table though, and you might break things by overwriting it (example, don't call
setmetatable on a Frame or ActionButton...)
All methods on a table can be called in two ways; with a colon, or with a period. The colon can be thought of as "fill in the first parameter with the table this is being called on". For example, these two statements are equivalent:
In the example above, the signature of
gsub is something like this:
The convention used is to call the first parameter
self. We can now use this colon notation with metatables to make our version of inheritance.
Due to the way the colon operator works, the
self parameter is filled in with the table calling the method, not the table the method is defined on. So calling
first:increase() is the same as
We can now take these elements, and craft a set of classes designed for reuse. We start off with our root object (think
System.Object if you are from a .net world.)
We have two methods here,
new method is nice and straight forward - it creates a new table, assigns the meta to be
class and calls the
ctor method (which is the one you would want to replace in sub classes).
extend method takes in a new table, and applies and sets the meta to
class. This is what is used to inherit and add new functionality.
For example, in my control library, I have a base class with some common methods:
And then many other classes which extend the base, cilling in the
ctor method with how to actually create the frame:
Some classes, such as the textbox provide other methods where they make sense.
Calling Base Class Methods
If we wish to start overriding a method and then call the original method within, things start to get a lot more complicated.
While this looks like it will work, it will cause some strange and hard to debug problems (I know it will, it took me ages to figure out.)
The problem is that when you do
self.base:method() you are effectively doing
self.base.method(self.base), which means the base method is referencing the wrong table!
We can solve this, but it requires a certain level of voodoo. First we need to change our
This took me far too long to come up with and get working. Essentially what it does is take all calls, and replace the
self parameter with the correct table.
This method has some restrictions, in that you can only go 'up' one level in the class hierarchy, e.g. you cannot do
item:super():super():super(). In practice though, I have never needed to do this.
The entirety of my class file can be found on my github.
There are two disadvantages to this method of creating objects. The first is using a table like this, you can no longer totally hide variables as you could do in the closure version. The other is the complexity added - especially if you wish to allow base method calling, however in balance, you only need to write the
super() functionality once (or use mine!)
When writing addons, I use both methods of encapsulation where they fit best - as like everything else in development the answer to what to use is "it depends".