I've recently found that by combining Closures with Metatables, I was able to create an inheritable object that supports get/set members much like a more typical object oriented language.
First, we start with the basic closure style definition. This is done by creating a table within the confines of a function. This allows us to define local variables within that function that will only be accessible from within functions added to the table from within the function. This would be a typical closure.
-- Closure function
function Class()
local _privateVariable = 1
local self = {}
self.getVariable = function()
return _privateVariable
end
return self
end
-- Create an instance of Class
object = Class()
print(object.getVariable()) --> 1
print(type(object._privateVariable)) --> nil
The primary advantage of this is that _privateVariable is completely inaccessible from outside the instance, which is a feature otherwise not present in Lua.
Where my particular implementation shines is in its use of metatables combined with the closure, as follows.
-- Closure function
function Class()
local _members = { }
-- Returns the instance
return setmetatable( { },
{
__newindex = function(self, k, v)
if k=="_member" then
assert(type(v)=="table", "Invalid member")
assert(v.key~=nil, "Invalid member")[/size]
_members[v.key]={get=v.get, set=v.set}[/size]
elseif _members[k] == nil then[/size]
rawset(self,k,v)[/size]
else
assert(_members[k].set~=nil,"Attempt to write to a read-only member: ",k)
_members[k].set(v)
end
end,
__index = function (self, k)
if _members[k]~= nil then
if _members[k].get~=nil then
return _members[k]:get()
end
end
end
})
end
instance = Class()
instance._member = {key="type", get=function() return "class" end}
print(instance.type) --> class
instance.type = "something else" --> crashes.
Lets break this down.
return setmetatable( { },
{
This combines these two lines from the previous example and generates the metatable:
local self = {}
return self
The metatable provides the following functionality:
__newindex = function(self, k, v)
This function is called any time something tries to write a value to a key that doesn't exist in the instance. For instance, if the "print(type(object._privateVariable))" line were called on the second example, it would call the __newindex member of the metatable. The self variable, of course, references the instance, k is the key and v is value to be written.
if k=="_member" then
assert(type(v)=="table", "Invalid member")
assert(v.key~=nil, "Invalid member")
_members[v.key]={get=v.get, set=v.set}
This section allows us to generate new members for the Class. It can be called from an instance of Class() or from another object that inherits from Class(). This would be called by "instance._member = { key="getSomething", get=function() return _privateVarialbe end }. If there were some private variable defined as _privateVariable, this member would make it accessible as a read-only member.
elseif _members[k] == nil then
rawset(self,k,v)
This allows classes inheriting this base class to create new items, as long as they don't interfere with existing members.
else
assert(_members[k].set~=nil,"Attempt to write to a read-only member: ",k)
_members[k].set(v)
end
end,
This crashes with a warning that the program tried to write to a read only member. This can be handy for debugging. If it doesn't crash, it calls the set function to handle the value. This can be used when changing a member needs to do more than just update a value.
Note: The assert statement can be changed for an if statement if you do not want the program to crash. This will just cause the instance to ignore any attempts to write to a read-only member. It might be best to tie this to a global "debugging" boolean. Hmm…
__index = function (self, k)
if _members[k]~=nil then
if _members[k].get~=nil then
return _members[k]:get()
end
This is called whenever a program tries to read a value from a key that doesn't exist in the object. If a member exists, and there is a get function for it, that function will be called.
This class really finds it's niche when inherited.
function InheritedClass()
local self = Class()
local _privateVariable = 3
local function _privateVariableSetter(v)
_privateVariable = 2 * v
end
Class._member = { key="var",
set=_privateVariableSetter,
get=function() return _privateVariable end }
return self
end
This class inherits all the functiony goodness of the Class() object, but adds a local private variable, as well as get and set accessors for it.
I hope you've enjoyed this, and I look forward to your feedback!
Edit: I can't seem to fix the tab issues. I copied in from Notepad++ which I have set up to replace tabs with spaces, and I tried deleting the lines and retyping spaces manually. Nothing works, but I got weird issues that showed up randomly. Any ideas on how to visually clean up my code blocks?