
Basic Concepts
All LOOP class models present a set of common functionalities, as is presented in this section. For instance, all models are presented as sub-packages of the LOOP package. Therefore, to require a particular model you should use the following command:
-- use the LOOP model named 'simple' -- for classes with simple inheritance local oo = require "loop.simple"
Declaring Classes
All object models currently provided by LOOP are based on classes to implement behavioral sharing. Therefore, to create a LOOP object you must first define a class for it using the operation class(definition)
. This operation receives a table that must be used as the class. Generally, a LOOP class can be seen as the meta-table of all objects of that class, i.e. it contains the meta-methods that defines the behavior of its instances. Since object classes usually are also used to define common features provided by all instances, every LOOP class has a default implementation of the __index
meta-method that retrieves fields from the class. This way, every field defined by the class is shared by all its instances. For example, suppose the following class definition.
-- model with no inheritance local oo = require "loop.base" local Date = oo.class{ -- default field values day = 1, month = 1, year = 1900, } function Date:addyears(years) self.year = self.year + years end function Date:__tostring() return string.format("%d/%d/%d", self.month, self.day, self.year) end
The class Date
defines the fields day
, month
, year
with default values and provides the implementation of the method addyear
and the __tostring
meta-method that is used by the tostring function of Lua base library. Therefore, all instances of class Date
will share this behavior.
Creating Objects
Suppose the following code that creates an instance of class Date
and uses it to find out the next birthday of a person.
function input(message, validator) print(message) return assert(validator(io.read())) end local day = input("Enter the day of your birth" , tonumber) local month = input("Enter the month of your birth", tonumber) local year = input("Enter the year of your birth" , tonumber) local age = input("Enter your age" , tonumber) -- new Date instance that overrides default values local birthday = Date { day = day, month = month, year = year, } birthday:addyears(age + 1) print("Your next birthday will be on", birthday)
All classes of LOOP models can be instantiated like in the example above that is using the class as a constructor that receives the values used to create an instance and returns it. Alternatively, instances can be created by the operation new
that receives as parameters the class and the values used to create the instance.
Initializing Objects
The default way instances are created is using the first argument informed as the object that must become an instance of the class. That is why the above example passes a table for the class in order to create the instance. However, this behavior can be redefined by the definition of the __init
field on the class. This field works like a meta-method that is used to create instances of a class. The function stored at field __init
receives the class and all the values provided to the class constructor. For example, suppose that we want to change the class Date
so it can be instantiated from a tripe of numbers instead of a table containing possible replacements of default attribute values, then we can use the code provided below.
function Date:__init(month, day, year) -- self is the class return oo.rawnew(self, { day = day, month = month, year = year, }) end
The rawnew
function is used to forcefully turn a table into an instance of a class without calling the __init
function. The rawnew
function can be roughly comparable to the setmetatable function of Lua with switched parameters (actually that is its implementation in most LOOP models). The main purpose of the __init
function is to initialize a table so it can be turned into an instance of a class. This way the class may define may different semantics for the constructor. For example, consider the two following types of constructors.
function Constructed:__init(...) -- creates and initialize a brand -- new instance with the values of -- provided parameters return oo.rawnew(self, { ... }) end function Mutant:__init(object) object = object or {} -- optionally checks if the object -- presents the expected state -- and turns it into an instance return oo.rawnew(self, object) end
When Constructed
constructor is called, it always return a brand new instance, so it is not possible to properly make an existing table an instance of Constructed
(unless using the rawnew
function). On the other hand, the constructor of Mutant
class can be used to initialize the table properly prior to the actual instantiation performed by rawnew
.
Introspection Mechanisms
Finally, all LOOP models provide introspection operations like classof(object)
, isclass(table)
, instanceof(object, class)
, memberof(class, name)
and members(class)
for retrieving the LOOP class of an object, check whether a table is a class of the LOOP model, check whether an object is an instance of a given class, get the value of a member defined in a class, and iterating through all class members, respectively.
Copyright (C) 2004-2008 Tecgraf, PUC-RioThis project is currently being maintained by Tecgraf at PUC-Rio.