In the last post I alluded to the fact that if you put in a little leg work, you could write well encapsulated objects in lua. There are two main ways to do this; with closures, and with metatables. In this post we will deal with using closures, and in the next post we will cover using metatables.
The simplest way to write an object in lua is with a closure to hide all the variables from the outside world. For example, we can write a counter class like so:
We are using a table to give us a class name, and the closure is the only method on it (called
new). My standard convention is to call the actual object we return
this object contains the public surface of our object, in this case two methods called
print(). You can use the counter like this:
By using a closure, we limit the use of the
count variable to only methods defined in the body of the function
new. This prevents anyone who uses the class from knowing how it is implemented, which is important as we are now at liberty to change the implementation without affecting our users.
A good example of this technique is in my Dark.Combat addon. While writing cooldown tracking, I needed to know how many stacks of Maelstrom Weapon was the maximum, so that I could trigger a glow effect on the icon. The problem is that the Warcraft API doesn't have a way of querying this (you can call GetSpellCharges for spells such as Conflagurate, but sadly this doesn't work on an aura.)
To solve this, rather than hard coding values into the view, or forcing the user to specify some kind of "glow at xxx stacks" parameter in the config, I wrote an object which you can be queried. This could also be expanded later to hold additional spell data which is not available in the API.
As the implementation of
getMaxCharges is hidden, I can change it at will - perhaps splitting my
charges table into two separate tables, or if Blizzard kindly implemented a
GetMaxStacks(spellName) I could call this instead and remove my
charges table altogether.
We can utilise composition to create objects based off other objects, by decorating an instance with new functionality. A slightly cut down version of the grouping code from my Dark.Bags addon makes good use of this:
Here we have two classes
group acts as our base class; it just creates a frame, and initialises a layout engine which does the heavy lifiting of laying out child frames.
bag.new() function, we create an instance of a
group and add a
populate method to it, and return it. We can continue creating new classes which use
group as base types as we need.
Problems with Closures
The down side to using closures is that inheritance is not really possible. To take the
counter example again, if you wanted to create a stepping counter, you couldn't do this:
Not only can you not access the original
count variable, but you would also have to reimplement the
These problems can be solved using the metatables methods in the next post, however depending on what you are doing, you could just use composition instead as outlined below.