Class Models for Lua

# Class Features

This section presents some advanced features and uses of the LOOP models.

## Class Changes

As stated previously, LOOP models are devised mainly to be dynamic therefore it is possible to apply changes over classes. In fact, every model allows that changes over classes may be reflected over all instances. For example, if a field of a class is changed, the new value is available to all instances automatically. This is particularly true in the cached and scoped model that even though they use caches of inherited fields, the use of class proxies to intercept changes over classes allows that caches can be updated.

## Class Invocations

Method calls over classes of LOOP models implies function calls with the self parameter set to the class object. This way, changes applied to the self parameter implies changes on the class object and therefore reflects over all class instances. For example, suppose the class `Date` presented on section Basic Concepts, then the code below

    Date:addyears(100) -- changes the default values of
                       -- field 'year' from 1900 to 2000

will change the default value of the field `year` to 2000 so any `Date` instance that does not override the value of field `year` will reflect this change.

## Interoperability

All LOOP models are interoperable, that is, a class from one model can be used as a base class of another and objects from different models can be mixed together to compose an application. This is possible because a LOOP class is seen as a plain Lua table that is used to retrieve field values.

However there are some drawbacks. For instance, classes from models other than the `scoped` model only inherits public members from classes of model `scoped`. Other limitation is that `cached` and `scoped` class fields always override the fields of classes from other models, no matter their order in the list of super-classes. For example, consider the following code.

    local BaseSuper   = loop.base.class  { modelname = "simple" }
    local CachedSuper = loop.cached.class{ modelname = "cached" }
    local MultipClass = loop.multiple.class({}, BaseSuper, CachedSuper)
    local CachedClass = loop.cached.class  ({}, BaseSuper, CachedSuper)

    local nocache = MultipClass()
    local cached  = CachedClass()

    print(nocache.modelname) --> simple
    print(cached.modelname)  --> cached

The `nocache` object does not use a cache of fields, so it looks up the class hierarchy to find the value of field `modelname` and finds it in the `BaseSuper` class. However, the `cached` object uses a cache of inherited fields that only contains fields from cached classes (i.e. `cached` and `scoped` models) therefore it finds the value of field `modelname` provided by class `CachedSuper` even though it is after the `BaseSuper` class in the list of super-classes of `CachedClass`.

## Packaged Classes

LOOP classes can be packaged using the `module` function provided by Lua 5.1. The simplest way to pack classes using the module function is using the class constructor as a module option. For example, consider que code below.

    local string = require "string"
    local oo = require "loop.base"

    module("chrono.Date", oo.class)

    day = 1
    month = 1
    year = 1900

    function addyears(self, years)
      self.year = self.year + years
    end

    function __tostring(self)
      return string.format("%d/%d/%d",
                           self.month,
                           self.day,
                           self.year)
    end

However, this approach does not allow to define super-classes. To avoid this, use the module variable `_M` as the class definition table. For example, consider the following class.

    local oo = require "loop.simple"

    module "chrono.SumDate"

    oo.class(_M, require("chrono.Date"))

    function __add(self, other)
      assert(oo.instanceof(one, _M) and
             oo.instanceof(other, _M))

      -- missing implementation ...
    end

    function __sub(self, other)
      assert(oo.instanceof(one, _M) and
             oo.instanceof(other, _M))

      -- missing implementation ...
    end

The classes packaged using the module function can be used like in the following code.

    local SimpleDate = require "chrono.Date"
    local AddingDate = require "chrono.SumDate"
    local mydate1 = SimpleDate()
    local mydate2 = AddingDate()

## Super-Class Access

Most LOOP models provide functions for introspection of the class hierarchy, allowing the application to get the super-class of a particular class. However, the access to super-class in method implementations can be cumbersome in some situations. One main problem is that a class method is usually a plain Lua function and therefore is not bound to a particular class. This way, there is no simple way to determine the class of a method in order to figure out its super-class and the inherited method implementation. The most straightforward solution is to place super-classes in local variables and access the inherited methods using the class stored in that variable, like in the example of the following code.

    local Square = oo.class()
    function Square:draw()
      self.canvas:setcolor(self.color)
      self.canvas:rect(self.xpos - self.width /2,
                       self.ypos - self.height/2,
                       self.xpos + self.width /2,
                       self.ypos + self.height/2)
    end

    local Button = oo.class({}, Square)
    function Button:draw()
      Square.draw(self) -- calling inherited method
      self.canvas:setcolor(self.labelcolor)
      self.canvas:setfontsize(self.labelsize)
      local tw, th = self.canvas:textdim(self.label)
      self.canvas:text(self.xpos - tw/2,
                       self.ypos - th/2,
                       self.label      )
    end

The first line of method `Button:draw()` calls the method `Square:draw()` over the object instance using the value stored in upvalue `Square` to retrieve the implementation of operation `draw` available in class `Square`. However, this explicit reference to the super-class `Square` may be troublesome in the scenario of maintaining the code in face of changes on the class hierarchy. To avoid such problem the acquisition of the super-class can be done through the `Button` class stored in variable `Button`, like in the code below.

    local Button = oo.class({}, Square)
    function Button:draw()
      oo.superclass(Button).draw(self) -- calling inherited method
      self.canvas:setcolor(self.labelcolor)
      self.canvas:setfontsize(self.labelsize)
      local tw, th = self.canvas:textdim(self.label)
      self.canvas:text(self.xpos - tw/2,
                       self.ypos - th/2,
                       self.label      )
    end

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

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