BLOOM (as in Basic Lua Object Oriented Model) is a simple OOP layer for Lua language.
bloom is used to write routing algorithms for a softswitch VoIP Class 5. Although we can do without bloom, it greatly helps scripters can manipulate classes and objects that represent business concepts in a classic OOP manner, as
object = MyClass:instanciate(initParams)
results = object:method(parameters)
By providing a decent inheritance mechanism, bloom also speeds up routing specialization by reducing it to inheritances and method overloading.
bloom is a lot of FUN, in writing it, in playing with it ... in applying it to real world telecommunication software. I hope you will enjoy it.
The sample scripts must be executed from the directory examples/.
$ lua [-i] <example.lua> [args]
Use -i option to enter interactive mode after script has been processed.
bloom is a pure Lua module. As such, all you need is to ensure bloom.lua is in your package.path,
package.path = package.path .. ";/where/bloom/resides/?.lua"
then add require(bloom) in your own script files.
require("bloom")
bloom.loadClass("birds.Parrot")
The default behaviour of bloom is to look for classes in the current working directory. If we consider the preceding code snippet, saying we are in directory /home/nmichel/private/bloom/ (not in examples), bloom wont't be able to load class "birds.Parrot".
In such cases (as to use shared class libraries, ...) you have to add lookup paths.
package.path = package.path .. ";/where/bloom/resides/?.lua"
require("bloom")
bloom.addLookupPath("/usr/nmichel/private/bloom/examples")
bloom.loadClass("birds.Parrot")
One can define a class either by calling method makeClass on a instance of a metaclass, or by using class loading.
local HelloWorld = bloom.MetaClass:makeClass("HelloWorld", {bloom.Object},
{
__init__ =
function(self, who)
self.who = who
end,
salute =
function(self)
return tostring(self.who) .. " says \"Hello world!\""
end
})
local helloWorld = HelloWorld:instanciate("Donald")
local salute = helloWorld:salute()
print(salute)
Create a file HelloWorld.lua returning the definition of the HelloWorld class. Class name and file name must match.
return {
"HelloWorld",
{bloom.Object},
{
__init__ =
function(self, who)
self.who = who
end,
salute =
function(self)
return tostring(self.who) .. " says \"Hello world!\""
end
}
}
Then use the class loading mecanism to load the HelloWorld class.
require(bloom)
bloom.loadClass("HelloWorld")
local helloWorld = HelloWorld:instanciate("Donald")
local salute = helloWorld:salute()
print(salute)
A class can inherit another (or several). The former is called "derived class", while the latter is said to be the "base class". A derived class can redefine (or override) some methods of base classes. When a method is called on an object, the version of the method called depends on real type of the object.
A overridden method can call a base class's version, with self:super()(). By providing a class when calling super, one can indicate which base class version of the method must be called, e.g. self:super(inherit.advanced.OtherDerived)().
Lets have a base class called ... Base, defined in inherit/simple/Base.lua
return {
"Base",
{bloom.Object},
{
__init__ =
function(self, name)
self.name = name or "John Doe"
end,
myType =
function (self)
return "Base"
end,
says =
function(self, what, out)
return (out or print)(self.name .. " of class " .. self:myType() .. " says " .. tostring(what or "nothing"))
end
}
}
The method says() uses method myType() (amongst other details) to build a string passed to a function (if provided) or printed out. Note that myType() doesn't use base class Object to retrieve class name (as in self:getClass():getName()). This is because code is evaluated at runtime, so self:getClass() will return the real class, not Base as one might expect at first sight.
We define a class called Derived inheriting Base, in file inherit/simple/Derived.lua
return {
"Derived",
{inherit.simple.Base},
{
__init__ =
function(self, ...)
end,
myType =
function (self)
return "Derived (derived from " .. self:super()().. ")" -- Note call to base class version of myType() using self:super()()
end
}
}
Class Derived overrides method myType(), and uses self:super()() to call Base's version.
Now we can write a script which uses both classes, and calls says() on a instance of each of both.
bloom.loadClass("inherit.simple.Base")
bloom.loadClass("inherit.simple.Derived")
local b = inherit.simple.Base:instanciate("b")
local d = inherit.simple.Derived:instanciate("d")
local function says(who, what)
who:says(what)
end
says(b, "Hello world!")
says(d, "Hello world!")
Lets modify class Derived, to simply overrides myType() and add method foo().
// ...
myType =
function (self)
return "Derived"
end,
foo =
function (self)
return "foo"
end
We define class OtherDerived also inheriting Base. OtherDerived is nearly identical to Derived.
// ...
myType =
function (self)
return "OtherDerived"
end,
bar =
function (self)
return "bar"
end
Note that both Derived and OtherDerived redefine myType().
Now we define class MultiDerived inheriting Derived and OtherDerived.
return {
"MultiDerived",
{inherit.advanced.Derived, inherit.advanced.OtherDerived}, -- Multiple inheritance
{
__init__ =
function(self, ...)
end,
myType =
function (self)
local res = ""
for _, v in pairs(self:getClass():getSuperClasses()) do
res = res .. " " .. self:super(v)() -- Calling myType() of each base class
end
return "MultiDerived (" .. res .. " )"
end
}
}
Simple isn't it ? The interesting point is how myType() is redefined in MultiDerived. It illustrates the way you can select a base class version of an overridden method, with self:super()(...).
Here follows a client script, which uses all these classes.
bloom.loadClass("inherit.advanced.Base")
bloom.loadClass("inherit.advanced.Derived")
bloom.loadClass("inherit.advanced.OtherDerived")
bloom.loadClass("inherit.advanced.MultiDerived")
local d = inherit.advanced.Derived:instanciate("d")
local od = inherit.advanced.OtherDerived:instanciate("od")
local md = inherit.advanced.MultiDerived:instanciate("md")
print("d:foo()", pcall(d.foo, d))
print("od:foo()", pcall(od.foo, od)) -- Fail
print("d:bar()", pcall(d.bar, d)) -- Fail
print("od:bar()", pcall(od.bar, od))
print("md:bar()", pcall(md.foo, md))
print("md:foo()", pcall(md.bar, md))
local function says(who, what)
who:says(what)
end
says(d, "Hello world!")
says(od, "Hello world!")
says(md, "Hello world!")
Basically, a metaclass is what you use to build classes. But the metaclass which has build a class also defines the behaviour of this class in every occasion, e.g. how the class finds a method in its set of base classes or (soon) how a method is invoked on a instance.
You can define a new metaclass class by inheriting from existing one(s). Then you just have to instanciate it to be able to create new classes using your brand new metaclass.
Currently bloom.MetaClass is somewhat special as it is not a instanciation of a MetaClass class, and you can not instanciate it. It is a singularity in the model that may change in the future.
- Inspection
- More examples