Łukasz Gruner
Łukasz Gruner

Reputation: 3179

How can one implement OO in Lua?

Lua does not have build in support for OO, but it allows you to build it yourself. Could you please share some of the ways one can implement OO?

Please write one example per answer. If you have more examples, post another answer.

Upvotes: 23

Views: 18695

Answers (6)

Leslie Krause
Leslie Krause

Reputation: 467

For me, encapsulation and decoupling are the most important factors when I'm implementing classes in Lua. So for most situations, using a closure-based approach will suffice when I'm willing to sacrifice a bit of speed and memory for the convenience of a very robust self-contained environment for my class methods and properties.

Consider this example of an accounting class:

function Account(self)
    local balance = 0

    self.deposit = function (v)
        balance = balance + v
    end

    self.withdraw = function (v)
        if v > balance then error"insufficient funds" end
        balance = balance - v
    end

    return self
end

Now compare and contrast to the mostly equivalent metatable approach:

Account = {balance = 0}

function Account:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
            return o
end

function Account:deposit (v)
    self.balance = self.balance + v
end

function Account:withdraw (v)
    if v > self.balance then error"insufficient funds" end
    self.balance = self.balance - v
end

The biggest pro of closures is that they afford a truly private context for the object to do its work without interference. No additional boilerplate code is required.

However, the con is that each instantiated object results in new multiple closures. Such additional overhead may or may not be acceptable, depending on your application.

Upvotes: 1

kikito
kikito

Reputation: 52621

This is already answered, but anyway, here's my oop implementation: middleclass.

That lib provides the bare minimum for creating classes, instances, inheritance, polymorphism and (primitive) mixins, with an acceptable performance.

Sample:

local class = require 'middleclass'

local Person = class('Person')

function Person:initialize(name)
  self.name = name
end
function Person:speak()
  print('Hi, I am ' .. self.name ..'.')
end

local AgedPerson = class('AgedPerson', Person) -- or Person:subclass('AgedPerson')

AgedPerson.static.ADULT_AGE = 18 --this is a class variable
function AgedPerson:initialize(name, age)
  Person.initialize(self, name) -- this calls the parent's constructor (Person.initialize) on self
  self.age = age
end
function AgedPerson:speak()
  Person.speak(self) -- prints "Hi, I am xx."
  if(self.age < AgedPerson.ADULT_AGE) then --accessing a class variable from an instance method
    print('I am underaged.')
  else
    print('I am an adult.')
  end
end

local p1 = AgedPerson:new('Billy the Kid', 13) -- this is equivalent to AgedPerson('Billy the Kid', 13) - the :new part is implicit
local p2 = AgedPerson:new('Luke Skywalker', 21)
p1:speak()
p2:speak()

Output:

Hi, I am Billy the Kid.
I am underaged.
Hi, I am Luke Skywalker.
I am an adult.

Upvotes: 8

lfzawacki
lfzawacki

Reputation: 1452

I like to think of OOP as being the encapsulation of data inside a container (the Object) coupled with a subset of operations that can be done with this data. There IS a lot more to it, but let's assume that this simple definition is all and build something in Lua from it (also some familiarity with other OO implementations can be a nice boost for the reader).

As anyone with a little exposure to Lua may know, tables are a neat way to store key-value pairs and in combination with strings, things start to become very interesting:

local obj = {} -- a new table
obj["name"] = "John"
obj["age"] = 20
-- but there's a shortcut!
print("A person: " .. obj.name .. " of the age " .. obj.age)

String values as keys in a table can be accessed in a way very alike to the members of a struct in C or the public members of an object in C++/Java and similar languages.

And now for a cool magic trick: let's combine this with anonymous functions.

-- assume the obj from last example
obj.hello = function () 
   print("Hello!")
end

obj.goodbye = function ()
   print("I must be going.")
end

obj.hello()
obj.goodbye()

Awesome right? We now have means of having functions stored inside our tables, and again you can see it resembles how methods are used in other OOP languages. But something is missing. How can we access the data that belongs to our object inside our method definitions? This is generally addressed by changing the signature of the functions in the table to something like this:

-- assume the obj from last example
obj.inspect = function (self)
   print("A person: " .. self.name .. " of the age " .. self.age)
end

obj.hello = function (self) 
   print(self.name .. ": Hello! I'm " .. self.name)
end

obj.goodbye = function (self)
   print(self.name .. ": I must be going.")
end

-- now it receives the calling object as the first parameter
obj.inspect(obj) -- A person: John of age 20
obj.hello(obj) -- John: Hello! I'm John
obj.goodbye(obj) -- John: I must be going

That solves it in a simple manner. Maybe drawing a parallel to the way things work in Python (methods always get a explicit self) can aid you in learning how this works in Lua. But boy, isn't it inconvenient to be passing all these objects explicitly in our method calls? Yeah it bothers me too, so there's another shortcut to aid you in the use of OOP:

obj:hello() -- is the same as obj.hello(obj)

Finally, I have just scratched the surface of how this can be done. As has been noted in Kevin Vermeer's comment, the Lua Users Wiki is an excellent source of information about this topic and there you can learn all about how to implement another important aspects of OOP that have been neglected in this answer (private members, how to construct objects, inheritance, ...). Have in mind that this way of doing things is a little part of the Lua philosophy, giving you simple orthogonal tools capable of building more advanced constructs.

Upvotes: 38

Uri Cohen
Uri Cohen

Reputation: 3608

The best solution I saw is not to implement OO in Lua, where it is not natural and patchy, and hence takes many lines; rather, implement it in C++ using luabridge or luabind, where it is natural and powerful!

A minimalistic example which uses LuaBridge:

m.class_<MyClass>("MyClass")
.constructor<void (*) (/* parameter types */)>()
.method("method1", &MyClass::method1)
.property_rw("property2", &MyClass::getter2, &MyClass::setter2)
.property_ro("property3", &MyClass::property3)

This would translate into natural lua syntax:

c=MyClass()
c.method1()
c.property2 = c.property3 * 2
do_stuff(c.property3)

Also one-level inheritence is supported...

Upvotes: 0

jpjacobs
jpjacobs

Reputation: 9549

The approach I use usually goes like this:

class = {} -- Will remain empty as class
mt = {} -- Will contain everything the instances will contain _by default_

mt.new = function(self,foo)
    local inst={}
    if type(foo) == "table" then
         for k,v in pairs(foo) do
             inst[k]=v
         end
    else
        inst.foo=foo
    end
    return setmetatable(inst,getmetatable(class))
end

mt.print = function(self)
    print("My foo is ",self.foo)
end

mt.foo= 4 --standard foo

mt.__index=mt -- Look up all inexistent indices in the metatable

setmetatable(class,mt)

i1=class:new() -- use default foo
i1:print()

i2=class:new(42)
i2:print()

i3=class:new{foo=123,print=function(self) print("Fancy printing my foo:",self.foo) end}

Well, conclusion: with metatables and some clever thinking, about anything is possible: metatables are the REAL magic when working with classes.

Upvotes: 2

Nick Van Brunt
Nick Van Brunt

Reputation: 15464

For a quick and dirty oo implementation I do something like -

function newRGB(r,g,b)
  return {
    red=r;
    green=g;
    blue=b;
    name='';
    setName = function(self,name)
      self.name=name;
    end;
    getName = function(self)
      return self.name;
    end;
    tostring = function(self)
      return self.name..' = {'..self.red..','..self.green..','..self.blue..'}'
    end
  }
end

which can then be used like -

blue = newRGB(0,0,255);
blue:setName('blue');

yellow = newRGB(255,255,0);
yellow:setName('yellow');

print(yellow:tostring());
print(blue:tostring());

for a more full featured approach I would use an oo library as was mentioned by eemrevnivek. You can also find a simple class function here which is somewhere between full on library and quick and dirty.

Upvotes: 13

Related Questions