EmmanuelOga wrote:Hey, thanks for sharing this. In my case I like to use closures for building objects in a more raw style.You don't really need full blown classes while working with a prototypical language like lua.
The two guidelines I follow are:
* Choose composition over inheritance
* Does that really need to be private?
When using inheritance with the closure approach, I simply create a second factory function to instantiate the 'superclass' and then modify it to my heart content. Here is an example of Square-is-a-Shape and so on.
http://pastie.org/1659847
In regard to speed/memory consumption, in general using closures for building objects may take a little more memory as you may be creating more stuff in each instantiation (methods are new functions for every object). The advantage is that lots of stuff can be saved in the object closure, which is often faster for lua to access.
I would not mind a lot about 'speed' though. The differences in the synthetic benchmark in the article you mention are 47secs versus 38secs
for 1.000.000 iterations and 48kb versus 61Kb
for 100.000 objects. It does not seem like it would affect a lot most 'normal', 30/60FPS games. I want to think most games won't be creating and getting rid of even 10.000 objects per frame (if you game does... you really have a problem with either approach
) .
The main advantage of this approach is its 'cleanness' (subjective, I know), and that you don't really need any boilerplate code to use it.
You don't really
need a full blown class system, but I love having one (I'm an OOP nut-head of sorts).
The funny thing is that ClosureClass gets even worse speed and memory consumption. Here's the test code I'm running:
Code: Select all
args = {...}
if args[2] == 'memory' then
if args[1] == "middleclass" then
require('middleclass.init')
A = class('A')
B = class('B', A)
t = {}
function A:method()
return 1 + 1
end
function B:b()
return 2 + 2
end
for i = 1, 1000000 do
table.insert(t, A:new())
table.insert(t, B:new())
end
else
require('closureclass.init')
t = {}
A = class('A', function(self)
function self.method()
return 1 + 1
end
end)
B = class('B', A, function(self)
function self.b()
return 2 + 2
end
end)
for i = 1, 1000000 do
table.insert(t, A.new())
table.insert(t, B.new())
end
end
print("Memory in use: " .. collectgarbage("count") .. " KB")
else
if args[1] == "middleclass" then
require('middleclass.init')
A = class('A')
B = class('B', A)
function A:method()
return 1 + 1
end
function B:b()
return 2 + 2
end
instance = A()
for i = 1, 10000000 do
A:method()
end
else
require('closureclass.init')
A = class('A', function(self)
function self.method()
return 1 + 1
end
end)
B = class('B', A, function(self)
function self.b()
return 2 + 2
end
end)
instance = A.new()
for i = 1, 10000000 do
instance.method()
end
end
end
Results:
Code: Select all
$ time lua test_oop.lua middleclass memory
Memory in use: 235941.64257812 KB
real 0m3.822s
user 0m3.479s
sys 0m0.334s
$ time lua test_oop.lua closureclass memory
Memory in use: 509374.98046875 KB
real 0m8.059s
user 0m7.383s
sys 0m0.668s
Not very good.
EDIT: Oh yeah, my computer is Mac Pro 1,1 running two Intel Xeon 2.66 Ghz Dual Cores.
Indeed, I think, that closures look a lot nicer, when declaring classes and calling methods. The syntax shown for ClosureClass above really appeals to me, in fact I was even going to try to achieve this:
Code: Select all
class('Name' < SuperClass, initFunc)
But Lua wouldn't allow me to redefine string's metatable's __lt method.
kikito wrote:The idea of using a function scope is very nice. It didn't occur to me.
BlackBulletIV wrote:
By the way, MiddleClass is pretty jolly fast. It can create 2,000,000 instances of two different classes (1m for each class) in 3.8 seconds. Not bad.
EDIT 2: Hang on. Accessing and calling methods in ClosureClass takes about 4/5 the speed of MiddleClass.
Can you show us the tests you are using?
Regarding speed - on middleclass, the more "deep" your method is on the class hierarchy, the more expensive it is to use it. More classes have to be "traversed".
Code: Select all
A = class('A')
function A:foo() print('foo') end
B = class('B', A)
C = class('C', B)
a = A:new()
c = C:new()
-- a:foo() is faster than c:foo(), because there are 2 "indirections" less
That is were middleclass should perform the worst. I see that you are copying the module functions & class members when creating a subclass, as well as creating new implementations of new and include. If I'm reading that correctly, that should take care of the indirections, at the cost of taking more memory, and some flexibility - since there are no indirections, if you change the superclass, the subclasses don't notice it.
Code: Select all
A = class('A')
function A:foo() print('foo') end
B = class('B', A)
-- we change A again
function A:foo() print('bar') end
a = A:new()
b = B:new()
a:foo()
b:foo()
On middleclass that should print 'bar', 'bar'. I think on ClosureClass it might print 'bar', 'foo' (can't test it now). This is not very important unless you want to be able to do monkeypatching on classes.
It should be possible to increase speed even further by copying the methods directly to the instances. But that would consume even more memory*. By the way, I see that right now metamethods are copied into each instance. Is that intended?
Sorry if I didn't understand everything correctly and I talked nonsense.
*: Middleclass is called middleclass because it's intended to be the "middle point" between that (copying all methods to each instance, gaining speed but losing flexibility) and having a full-fledged Object Model (where you have a Class class, and classes are instances of it, like in ruby).
Indeed, this is one big problem with closures, instances won't respond if you modify the class, which is a shame.
I'm not sure what you're talking about by copying methods directly to instances. If you're talking about the code the copies stuff from superclass to subclass, that's for inheriting class methods and attributes (and also modules).
I'm guessing metamethods are copied, I'm not quite sure how it works in MiddleClass (don't you just define an instance function). Anyway, metamethods are yet to be handled.
Thanks for the feedback guys!