classes.lua - A simple Class implementation in Lua for Object-Oriented Programming

Posted by Varen, Posted on December 4, 2011, Last updated December 6, 2011

GitHub URL: 
https://gist.github.com/1429475

What?

classes provides a simple solution to Object-Oriented Programming in Lua. It has the basic OOP features one might expect, such as inheritance, and covers up dealing with metatables and the like.

Why?

classes was developed because when I started working with Corona, I had only a basic knowledge of Lua. Working on big projects, in any language, can usually benefit from some OOP functionality. There are a few libraries out there that bring this to Lua. This is a simple, lightweight, ~100 line implementation.

How?

Using the module

Well, first download classes.lua from github. Then, where ever you want to use it in your code, import it like any other Lua module:

1
local classes = require "classes"

Creating a Class

Creating a Class is pretty straightforward. Heres why:

1
2
3
-- The function classes.class() returns a new Class object.
-- In this case, our class is stored in the variable 'Animal'.
local Animal = classes.class()

Alternatively, you may want to create a class that has a Superclass. That's easy as well:

1
2
-- Cat is now a Subclass of Animal.
local Cat = classes.class(Animal)

If no Superclass is provided to the function, classes.Object is used as the Superclass. This basic Class has some predefined methods, but we will get to those later.

Instance Methods

If you have ever looked into Corona's event system, which is inevitable, then you should be familiar with table listeners. classes uses the underlying concept of these for instance methods. When an instance method gets called, it automatically has access to the self variable - the variable for that specific instance of the Class.

Here is an example of creating a instance method:

1
2
3
4
5
--- This is an instance method specific to the Cat class.
-- ':', not '.'!
function Cat:meow ()
        print("meow "..self.name.." meow")
end

Class Methods

If you come from a language like Java or C#, a class method is simply a static method. These methods do not have access to the self variable, as they apply to the whole class, and not a specific instance.

The syntax is similar to an instance method, with a minor difference. A '.' is used instead of accessing the method via a ':'. This is because the method does not need to know what self is, it represents the whole class!

1
2
3
4
5
6
7
--- This is an example of a Class method.  We can't access 'self', because it represents the whole Class, not an instance of this Class.
-- '.' not ':'!
--
-- @return Returns the total number of Animals.
function Animal.totalAnimals ()
        return numAnimals
end

Constructors

Constructors are a special method that creates a new instance of a Class. In classes, that method is known to the Class as init.

1
2
3
4
5
6
7
8
9
--- Constructor.  In addition to a name, it also takes in a cat breed.
--
-- @param breed The breed of the cat.
function Cat:init(name, breed)
        -- Make sure to call the super constructor!
        self.super:init(name)
        -- Next, do some custom instantiation.
        self.breed = breed
end

Notice the init method is being called on some self.super variable. This is just calling the constructor on the Class' Superclass. It is generally best practise, and sometimes required, to call the Superclass' constructor as the first thing you do in your constructor. More on the self and super variables later.

Constructors are optional. If you don't supply your own constructor, it will try to call the Superclass' constructor, and so on, until it finds one.

Creating a Class Instance

To create an instance of a Class, simply utilize the new method on the class object. Notice how the new method is a class method and not instance method. This means it should be accessed via '.' and not a ':'.

1
2
-- Create an instance of the Class Cat.  Pass the constructor a string called "mio".
local myCat = Cat.new("mio")

But this isn't the init function! Don't worry, this function calls your init function that you created after it takes care of some stuff. It passes the parameters you passed into the new function, too.

Instance variables

Inside any instance method, instance variables can be set by declaring them in the self variable. These variables belong to the object, not their defining class.

This is a good time to explain the self variable. If you are not familiar with it, it refers to the actual instance of the class you are currently operating on. For instance, if you had two instances of the Cat class: myCat and yourCat, the self variable refers to the respective cat object when you invoke myCat:meow() or yourCat:meow(). When you call myCat:meow(), you can check that self equals myCat. When you call yourCat:meow(), self is equal to yourCat. This variable is only visible inside an instance method, not class methods or outside methods.

The super variable is similar to the self variable, except it refers to the Superclass of the current object you are operating on. You must access this method through the self variable. For instance, lets say we defined an init method in the Animal class.

1
2
3
4
5
6
7
8
9
10
function Animal:init(name)
    self.name = name
end
 
...
 
function Cat:init(name, breed)
    self.super:init(name)
    ...
end

Here, we first call our Superclass' constructor. Because Animal happens to the the Superclass, this takes the name argument we received and sets it as an instance variable. It is important to note that self refers to the same object when it is used in a super method.

Class Variables

These, a Java developer would call them (maybe) are static variables. They belong to the class, not an instance of that class.

Here is an example for counting the number of objects you create of a certain type of Class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Animal.numAnimals = 0
 
Animal:init(name)
    self.name = name
    Animal.numAnimals = Animals.numAnimals + 1
    self.id = Animal.numAnimals
end
 
--- This method is available to the Animal class, and all Subclasses of Animal as well.
--
-- @return Returns the id of this Animal.
function Animal:getAnimalId ()
        return "Animal<"..self.id..">"
end

You access these variables via the name of the class, as seen above. These variables are public, anyone who has access to the class, has access to that variable.

To create a private static variable, use the local keyword and drop the class name as such:

1
2
3
4
5
6
7
local numAnimals = 0
 
Animal:init(name)
    self.name = name
    numAnimals = numAnimals + 1
    self.id = numAnimals
end

NOTE: These variables can be accessed by any code in the same file as the class declaration. So if you want 'true' private static variables, put all of the class definition in a separate file from anything else!

Instance Type Checking

To check if an object is an instance of, or directly or indirectly a subclass a Class, use the instanceOf method:

1
2
3
4
5
-- Returns true,  a Cat is also an Animal.
myCat:instanceOf(Animal)
 
-- Returns false, an Animal is not also a Cat.
myAnimal:instanceOf(Cat)

Notice how we pass the Class object, not an instance of the object, to the method.

Anything else I should know?

Yea. Avoid using these method names in a class:

  • new
  • alloc

They are reserved for use in the classes library for instantiating objects.

Also, I wouldn't try changing these variables of an object/class value:

  • super
  • class

As you know super is a reference to the supertype, i.e. if mio is an instance of Cat, then mio.super is an instance of Animal, which gives the object the ability to call super methods, as seen before.
The class variable is a reference to the class object, i.e. mio.class == Cat.

Who?

Well, I think that covers it. Direct any questions or feature requests to paul.andrewharrison.moore@gmail.com.

Cheers,
Paul


Replies

dejay.clayton
User offline. Last seen 4 years 25 weeks ago. Offline
Joined: 24 Jun 2011

Nice!

You should check out my open source library, CooL - Corona Object-Oriented Lua (https://github.com/dejayc/CooL), which uses a similar set of abilities for classes, including the ability for subclasses to "extend" base classes, with some performance optimizations built in.

There are also some framework aspects built-in, such as the use of Java-like classpaths, and dedicated objects to control app behavior, display behavior, etc.

hudson.graham
User offline. Last seen 1 year 42 weeks ago. Offline
Joined: 8 May 2011

Nice work guys!

dejay.clayton, I'd be very interested in seeing an example of your CooL framework in action (e.g. how to make a class, how to extend it, how to make display based class and how they are all added to the app instance).

Cheers