The most recent version of this document can probably be found at http://mnoo.luaforge.net/index.html. It was compiled February, 19 2006.
The purpose of mnoo is to provide an object oriented framework for the lua language.
It is closely related to the mnoofltk GUI library, which is built on top of mnoo.
Its object-oriented functionality can be used both with table and userdata type lua structures.
mnoo is "beta" status. It hasn't been tested in large scale applications yet.
The mnoo library itself contains test code for the most relevant features: mnoo.lua
Other files (probably not very polished) can be found from mnoofltk's code generation utility: build_mnoofltk.lua (toplevel), libBuilder_cl.lua, libBuilderType_cl.lua and libBuilderFun_cl.lua
-- ######################################################################################## -- # Load library -- ######################################################################################## local mnoo=require("mnoo_current/mnoo.lua") |
A new class is created "from scratch" using the newclass() function of the mnoo library:
-- ######################################################################################## -- # Create a new base class -- ######################################################################################## local creature_cl=mnoo.newclass() |
As a typing convention, we use variable names ending in "underscore cl" to denote, that the variable contains a class.
A class function is declared, assigned and called using
my_cl:declareClassfunction('doSomething'). my_cl.doSomething=function(classarg, arg) my_cl:doSomething(arg) |
Redeclaring an already existing name (also from derived classes) is forbidden and causes an error.
Note, that the class appears as "classarg" argument. This is often needed, because the actual class, on which doSomething() is called may be derived from my_cl. One may not assume, that my_cl equals classarg (but classarg "is a" my_cl).
It is good practise to use "assert(obj:objectIsA(my_cl))" generously throughout the code, because it documents to the reader, what sort of object the variable contains.
Assign: my_cl.name1=function(self, ...) assert(self:objectIsA(my_cl)) ... end
The default constructor can be called with a single class argument:
local obj=my_cl:new()'') |
It is possible to use other names than "new" as constructor (simply declare it as classfunction).
Sometimes it is needed to forbid access to the default or inherited new{} constructor by assigning a function to it, that throws an error.
If called with two arguments, the object functionality gets attached to the given table argument:
local data={objdata1=10} local data2=my_cl:new(data) assert(data2==data) |
-- ######################################################################################## -- # Overloading and calling of class functions -- ######################################################################################## -- ######################################################################################## -- # Base class: -- ######################################################################################## local c=lib.newclass() c:declareClassfunction("cfun") c:declareObjectmethod("ofun") c.cfun= function(actualclass, args) assert(mnoo.classIsA(actualclass, c)) -- actualclass must be a child class of c return "onec" end c.ofun= function(self, args) assert(self:objectIsA(c)) -- class of self must be derived from c return "oneo" end -- ######################################################################################## -- # Derived class: -- ######################################################################################## local c2=lib.newclass(c) local superclass_cfun=c2.cfun c2.cfun= function() return superclass_cfun().."twoc" end local superclass_ofun=c2.ofun c2.ofun= function() return superclass_ofun().."twoo" end assert(c.fun()=="onec") assert(c2.fun()=="onectwoc") -- same for objects -- program is untested. |
To enable serialisation for a class, it must be given a unique ID, that is used to look up the class when restoring the data.
lib.enable_serialize(my_cl, "test") |
In most practical cases, it is not desirable to store the complete content of an object, but only selected fields. Therefore, only fields that are declared with my_cl:declarePermanentObjectdata('name1', ...) are saved and restored.
It is generated using the library function
local ASCII=mnoo.serialise(data) |
and the objects are rebuilt with the library function
local data=mnoo.deserialise(ASCII) |
When deserialise() is called, all classes must be loaded.
Below an example for the generated, serialized code. It contains a single object with four data fields:
return function(constructors) return constructors.test{["perm2"]="b",["perm1"]="a",["multi"]="d",["perm3"]="c"} end |
The section below deals with both how to use it and how it works internally at the same time, TODO.
When the code of an application grows in size, there is typically the following problem:
lua itself provides no mechanism for access control, as implemented in C++ through the the protected / private keywords.
mnoo provides an experimental mechanism that prevents calling object functions from outside a predefined private environment:
The file that implements the actual object functions generates a new private environment using
local env=mnoo.createPrivateEnvironment() (copy needed globals to env) setfenv(env) |
Private object functions are now declared as usual, and then set to private using
my_cl:setPrivate("objfun1", "objfun2", ...) |
From now on, any read access on an object will check, if the environment of the calling function matches one of the known "friendly" environments, and possibly produce an error.
Privacy is inherited, meaning that derived classes have access to the parent's class private functions.
As a technical note, implementing privacy for object functions is relatively easy, because they are never defined in the object and always trigger metamethods. Privacy for object data cannot be implemented the same way (or would be extremely inefficient).
Using args_cl allows to write code that is "human-readable", without having to look up the implementations of functions called.
Example:
-- ######################################################################################## -- # Example for args_cl -- ######################################################################################## -- load library local args_cl=require("mnoo_current/args_cl.lua") local myfun= function(t) local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++ local stringarg=t:get{name="stringarg", type="string"} local numarg=t:get{name="numarg", type="number"} local boolarg=t:get{name="boolarg", type="boolean"} assert(t:all_done()) -- +++++++++++++ arg handling ends ++++++++++++++++++++ print(stringarg, numarg, boolarg) return self end myfun{stringarg="one", numarg="1", boolarg=true} -- OK ("1" gets converted to 1) myfun{stringarg="one", numarg="1", boolarg=true, extra=1} -- causes error: extra argument myfun{stringarg="one", numarg="one", boolarg=true, extra=1} -- causes error: "one" is nonnumeric |
Obviously, it causes some overhead.
If performance is absolutely critical, don't use args_cl.
If performance is an issue, omit the all_done{} check.
[Top] | [Contents] | [Index] | [ ? ] |