Class Models for Lua

# Class Models

## Base Model

The LOOP models can be ordered in such a way that the next model provides the same features of the previous one. In this order, the first one is the `base` model that provides exactly the features presented in section Basic Concepts. The `base` model is ideal for definition of classes without super-classes.

### Functions

`class([table])`
  Returns the object that represent a class with the features defined by `table`. Changes on the object returned by this function implies changes reflected on all its instances. This object can also be used as a constructor of instances.
`classof(object)`
  Returns the class of `object`. If `object` is not a LOOP object (i.e. a usual table) then its meta-table is returned. Actually, this function is the `getmetatable` function of Lua for most of LOOP models.
`initclass(table)`
  Initialize table `table` as a class. It basically sets the value `__index` field of `table` to refer the table `table` itself, unless such field provides a value different from `nil`.
`instanceof(object, class)`
  Returns true if `object` is an instance of `class` and false otherwise.
`isclass(table)`
  Returns true if `table` is a class of the LOOP model and false otherwise.
`memberof(class, name)`
  Returns the value of member defined by `name` if it is defined in class `class`. Inherited members are ignored by this function.
`members(class)`
  Returns an iterator that may be used in a `for` statement to iterate through all the members defined by the class. The iteration variables hold the field name and value respectively.
`new(class, ...)`
  Returns an instance of class constructed accordingly to the values of the extra arguments.
`rawnew(class [, object])`
  Makes `object` an instance of `class` without calling the `__init` function of the class to initialize is state. Actually, this function simply sets the meta-table of `object` to table `class`. If no `object` is provided, a new table is created to represent the new instance.

### Example

    Cache = oo.class{ __mode = "k" }
    function Cache:__index(key)
      local retrieve = rawget(self, "retrieve")
      rawset(self, key, retrieve(self, key))
      return rawget(self, key)
    end

    Channels = Cache{}
    function Channels:retrieve(host)
      return assert(socket.connect(host, 80))
    end

    for _, request in ipairs(BatchProcess) do
      local c = Channels[request.host]
      assert(c:send(request.data))
      request.reply = assert(c:receive())
    end

## Simple Model

The next one is the `simple` model that adds the possibility of defining classes with simple inheritance. The `class` function of the `simple` model takes an optional second argument that defines the super-class of the class being created. Additionally, the `simple` model introduce the functions `superclass(class)` to retrieve the super-class of a given class and `subclassof(class, super)` to check whether a class is sub-class of other.

### Functions

All from `base` model and (re)defines:

`class(table [, super])`
  Returns the object that represent a new class that provides the features defined by `table` and that inherits from the class `super`. Changes on the object returned by this function implies changes reflected on all its instances. This object can also be used as a constructor of instances.
`subclassof(class, super)`
  Returns true if `class` is a sub-class of `super` and false otherwise.
`superclass(class)`
  Returns the super-class of `class`. If `class` is not a class of the model or does not define a super class, then it returns `nil`.

### Example

    Circle = oo.class()
    function Circle:diameter()
      return self.radius * 2
    end
    function Circle:circumference()
      return self:diameter() * 3.14159
    end
    function Circle:area()
      return self.radius * self.radius * 3.14159
    end

    Sphere = oo.class({}, Circle)
    function Sphere:area()
      return 4 * self.radius * self.radius * 3.14159
    end
    function Sphere:volume()
      return 4 * 3.14159 * self.radius^3 / 3
    end

    function show(shape)
      print("Shape Characteristics")
      print("  Side:         ", shape.radius)
      print("  Diameter:     ", shape:diameter())
      print("  Circumference:", shape:circumference())
      print("  Area:         ", shape:area())
      if oo.instanceof(shape, Sphere) then
        print("  Volume:        ", shape:volume())
      end
    end

    c = Circle{ radius = 20.25 }
    s = Sphere{ radius = 20.25 }

    show(c)
    show(s)

## Multiple Model

The third is the `multiple` model that enables the definition of classes with multiple inheritance. The `class` function of the `multiple` model takes a sequence of optional arguments that defines the set of super-classes of the class being created. The order of the super-classes provided defines the priority of field inheritance therefore the value of a inherited field is defined by the leftmost class that provides such field. The `multiple` model introduce the new function `supers(class)` that returns an iterator used to iterate through the list of direct super-classes of a class. Additionally, the `superclass(class)` function is changed so it returns the sequence of super-classes of a given class.

### Functions

All from `simple` model and (re)defines:

`class(table, ...)`
  Returns the object that represent a new class that provides the features defined by `table` and that inherits from all classes provides as additional arguments. Changes on the object returned by this function implies changes reflected on all its instances. This object can also be used as a constructor of instances.
`superclass(class)`
  Returns all the super-classes of `class`. If `class` does not define a super-class, then it returns `nil`.
`supers(class)`
  Returns an iterator that may be used in a `for` statement to interate over all super-classes of the class `class`.

### Example

    Contained = oo.class{}
    function Contained:__init(object)
      assert(object, "no object supplied")
      assert(object.name, "no name for object")
      assert(object.container, "no container for object")
      object.container:add(object.name, object)
      return oo.rawnew(self, object)
    end

    Container = oo.class{}
    function Container:__init(object)
      object = object or {}
      object.members = object.members or {}
      return oo.rawnew(self, object)
    end
    function Container:add(name, object)
      self.members[name] = object
    end
    function Container:search(path)
      local container, newpath = string.match(path, "(.-)/(.+)$")
      if container then
        container = self.members[container]
        if container and container.search then
          return container:search(newpath)
        end
      else
        return self.members[path]
      end
    end

    ContainedContainer = oo.class({}, Contained, Container)
    function ContainedContainer:__init(object)
      Contained:__init(object)
      Container:__init(object)
      return oo.rawnew(self, object)
    end

    Root = Container{}
    Folder = ContainedContainer{
      container = Root,
      name = "my_folder",
    }
    File = Contained{
      container = Folder,
      name = "my_file.txt",
      data = "Hello, I'm a file"
    }
    print(Root:search("my_folder/my_file.txt").data)

## Cached Model

In order to avoid the search through the complete hierarchy of classes every time a class field is indexed, LOOP provides the `cached` model. In this model, classes copy the fields defined by their super-classes to themselves (i.e. meta-table). This cache of inherited fields makes instances of classes with simple or multiple inheritance as efficient as classes of the `base` model. Other advantage of the cache model is that meta-methods like the `__index` can be shared across the hierarchy of classes because they are copied to each class (i.e. meta-table). Currently, Lua ignores the `__index` when accessging the fields of meta-tables (see the Lua manual).

On the other hand, to properly update the cache of inherited fields whenever a class is changed, the classes of this model are manipulated through proxies that intercept any changes and update all caches of inherited field through out the entire class hierarchy. This indirection makes class operation more expensive than in other models where classes are simple meta-tables. All functions of the `cached` model manipulates proxies of actual classes. The `cached` model introduces the new function `allmembers` that return an iterator for all members provided by a class, including the inherited ones.

### Functions

All from `multiple` model and (re)defines:

`allmembers(class)`
  Returns an iterator that may be used in a `for` statement to interate over all members provided by the class `class`, including the inherited ones. The iteration variables hold the field name and value respectively.
`getclass(class)`
  Returns the actual internal object that represents the cached class represented by proxy `class`. This method is used only by other class models that extend this one, e.g. the `scoped` model.
`subs(class)`
  Returns an iterator that may be used in a `for` statement to iterate through all the sub-classes of `class`. The `class` parameter must be the internal object that represents the actual class. The iteration variables hold only the internal object that represent each sub-class of `class`. Each sub-class is iterared only once. This method is used only by other class models that extend this one, e.g. the `scoped` model.

### Example

    CachedObject = oo.class()

    function CachedObject:__index(field)
      local value = oo.classof(self)[field]
      if value then rawset(self, field, value) end
      return value
    end

    CachedSphere = oo.class({}, CachedObject, Sphere)

    s = CachedSphere{ radius = 2 }
    show(s)

    print("Object fields:")
    for field, value in pairs(s) do
      print("", field, value)
    end

## Scoped Model

The last LOOP model provides features to define classes with private and protected access scopes. Each class of `scoped` model can provide a definition of a private behavior that will be perceived only by methods defined in that class and a protected behavior that will be perceived only by the methods of the object, i.e. will not be seen by functions not defined by some class inherited by the object. The private and protected behaviors are defined by fields `private` and `protected` that must contain tables defining the fields presented by the private or protected scope of the instances of that class. All the other fields are publicly available.

The fields publicly available are also available in the private and the protected scopes. Similarly, the fields available in the protected scope are also visible in the private scope. The scope management is done by replacing the self reference in the calls of instance methods. Therefore, in every call of a method the self is replaced by the proper scoped state object. The mapping of the private and protected scoped state of each object instance is automatically made by the `scoped` model. Additionally, the creation of the scoped states is done on demand, so such states are only created at invocation of methods defined in classes that defines private or protected states.

The `scoped` model was mainly devised for applications with objects written in Lua that must protect some internal state from unexpected accesses, like bad user script code or third-party interacting components that may not know or care about the internal object implementation. Unfortunately, the management of such scoped states is extremely expensive both in terms of memory and processing time when compared to other models. Therefore, the `scoped` model should only be used in applications that actually need such infrastructure.

Even though it is a hard task to provide arbitrary private and protected state in a programming model similar to the one used when applying an object-oriented style to Lua, there are many alternatives to implement objects with private state in Lua. Such alternatives include the use of function closures as objects with private state stored in upvalues. On the other hand, the `scoped` model may be used for prototyping and experimental applications used as proof of concept.

### Functions

All from `cached` model and (re)defines:

`priv(object [, class])`
  Returns the private state of `object` relative to class `class`. This `object` can be any `self` of a scoped object, i.e. the private, procetected or even public state. If no `class` is provided then the actual object class is used.
`prot(object)`
  Returns the protected state of `object`. This `object` can be any `self` of a scoped object, i.e. the private, procetected or even public state.
`this(object)`
  Returns the public referece of `object`. This `object` can be any `self` of a scoped object, i.e. the private, procetected or even public state.

### Example

    SQLTable = oo.class{
      private = {
        SQLTemplate = [[
          SELECT *
          FROM %s
          WHERE %s = '%s'
        ]],
      },
    }
    function SQLTable:__init(database, tablename, keyfield)
      self = oo.rawnew(self)
      rawset(oo.priv(self), "db", database)
      rawset(oo.priv(self), "table", tablename)
      rawset(oo.priv(self), "key", keyfield)
      return self
    end
    function SQLTable:__index(keyvalue)
      local sql = self.SQLTemplate:format(self.table, self.key, keyvalue)
      return self.db:query(sql)[1]
    end

    People = SQLTable(MySQLDB, "People", "Name")

    print(People["John Doe"].Age)

Copyright (C) 2004-2008 Tecgraf, PUC-Rio

This project is currently being maintained by Tecgraf at PUC-Rio.