OOP in Lua and Lua Class Lib

2011-06-01 Robert Zhang 更多博文 » 博客 » GitHub »

oo lua python

原文链接 http://huiming.io/2011/06/01/lua-oop.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


Project at GitHub

This article assumes readers have a basic knowledge on Lua. And to understand the "Design and Implementation", a good Lua knowledge is required and some knowledge on Python OOP is a plus.

What's OOP on earth? How to do it in Lua? And what's Lua Class Lib? Let's have an interesting tour to OOP in Lua.

  • 目录 {:toc}

A short story about OOP in Lua

"Lua is cool! But it lacks support for OOP!"

You may think that. Yes, I agree, if your criterion for OOP is just a "class" key word or something like that. It may disappoint you when you search for that in Lua official documents but find nothing you want.

The fact is Lua doesn't have a keyword for class, but Lua is really good at OOP.

Read on!

Object

Let's forget the superficial ideas of OOP and be after its essence:

What on earth does "object" mean?

In conception, it's just a thing that has properties and behaviors. In implementation, it is just a data structure saving properties, and some functions operating on it for behaviors. That's it!

According to the definition, Lua table is just a good data structure for OO:

t = {}                                           --t is an object
t.status = 100                                   --t has a property
t.method = function(self) print self.status end  --and a method

Class

Then what about the class?

In conception, it is simply a category: objects in the same category have something(properties and/or behaviors) in common. In implementation, it usually means some way to create an object of a category.

According to that, the object t in above code does have a class: an anonymous category in our mind. You can create another object in the same category, or "class", by:

t2 = {}                                           --t2 is another object of the same 'class' as t
t2.status = 101                                   --t2 has a different status
t2.method = function(self) print self.status end  --and the same method

And, you can make the code reusable:

function createA(status)
    local o = {}
    o.status = status
    o.method = function(self) print self.status end
    return o
end

t1 = createA(100)
t2 = createA(101)

Inheritance

And what about inheritance?

Again, let's clear the definition first. In conception, inheritance means some relationship among classes: class B inheriting class A means B has something(properties and/or behaviors) in common with A, and thus objects of class B behave much like those of class A. In implementation, it usually means some code organization.

Having that in mind, we can say t3 in the following code has a conceptual class that inherits t2's:

t3 = {}
t3.status = 10000
t3.method = function(self) print self.status end
t3.anotherStatus = 'hello'
t3.anotherMethod = function(self, n) self.status = self.status + n end

And we can make the code reusable:

function createB(status, anotherStatus)
    local o = createA(status)
    o.anotherStatus = anotherStatus
    o.anotherMethod = function(self, n) self.status = self.status + n end
    return o
end
t3 = createB(10000, 'hello')

That's the basic story of OOP in Lua. Not so appealing? I know you want more. Read on!

Lua Class Lib

But I want a more elegant way for OOP in Lua, as C++/Java/Python/... does.

I heard your heart and here's the Lua Class lib for your rescue.

Quick start

Here it is:

require 'cls'   --import Lua Class Lib

class 'Person'  --define a Class
{
    __init__ = function(self, name) --define an initializer
        self.name = name
    end;

    say = function(self)            --define a method
        print('Hello, my name is ' .. self.name .. '.')
        self:saySthElse()
    end;

    saySthElse = function(self)
    end
}
p = Person('Bob')  --create an object
p:say()            --call its method

Output:

Hello, my name is Bob.

Class inheritance:

class 'Employee: Person'  --a class inheriting class Person
{
    __init__ = function(self, name, salary, id)
        Person.__init__(self, name)  --call base class's method directly
        self.salary = salary
    end;

    saySthElse = function(self)      --override base class's method
        print('My salary is ' .. self.salary .. '.')
    end
}

e = Employee('Bob', 1000)
e:say()

Output:

Hello, my name is Bob.
My salary is 1000.

Even multiple inheritance:

class 'A' {...}
class 'B' {...}
class 'C: A, B' {...}
c = C()
assert(isInstanceOf(c, A))
assert(isInstanceOf(c, B))
assert(isInstanceOf(c, C))

Note that the class in above code is in fact a user defined function, not a keyword of the language! Really cool, isn't it? Thanks to the powerful language!

Let's move on to the implementation for the class function. Believe it or not: it's surprisingly short! Say it again: Lua is cool!

Design and Implementation

Lua doesn't provide a class keyword or something like that as other OO languages do, but Lua grants you much more power to shape your own OO implementation. The Lua Class Lib is just an example on how you can shape it. And it's also supposed to give you an idea of how powerful the language is.

The lib is inspired by and modeled on Python OOP. Although the two languages are quite different, it doesn't matter when it comes to the essence of OOP.

The major points are:

  • A class is an object(table in Lua) of properties and methods, which is shared by all its instances, and its subclasses.
  • An instance is also an object(table in Lua) of properties and methods.
  • When referencing a member of an instance, the member is first looked up in the instance, then its class, and the first find is returned.
  • When assigning to a member of an instance, the member is always stored in the instance.
  • The class definition and instantiation "syntax" should be easy to understand.
  • Keep it simple.

Here is the code behind the scenes:

local function parseName(str)
    local _begin, _end, cls = assert(str:find('%s*([a-zA-Z][a-zA-Z0-9_]*)%s*%:?'))
    if not str:find(':', _end) then
        return cls, {}
    end
    local bases = {}
    while true do
        local base
        _begin, _end, base = str:find('%s*([a-zA-Z][a-zA-Z0-9_]*)%s*%,?', _end + 1)
        if base then
            table.insert(bases, base)
        else
            break
        end
    end
    return cls, bases
end

local function create(t, ...)
    local o = {}
    if t.__init__ then
        t.__init__(o, ...)
    end
    return setmetatable(o, {__index = t, __class__ = t})
end

function class(name)
    local env = getfenv(2)
    local clsName, bases = parseName(name)
    for i, v in ipairs(bases) do
        bases[i] = env[v]   --Replace string name with class table
    end

    return function (t)
        local meta = {__call = create, __bases__ = bases}
        meta.__index = function(nouse, key)
            for _, cls in ipairs(meta.__bases__) do
                local val = cls[key]    --Do a recursive search on each cls
                if val ~= nil then
                    return val
                end
            end
        end
        env[clsName] = setmetatable(t, meta)
    end
end

The code is short but delicate, deserves a good read.

It's a minimal(and maybe adequate) implementation for OO in Lua, and there's always room to improve and extend it. However, before you do that, ask yourself "Do I really need that extension?" and "Will the extension make the whole system too complicated?" Have fun!