Created
December 15, 2011 18:32
-
-
Save bjorn/1482246 to your computer and use it in GitHub Desktop.
Lua OOP system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- | |
-- Object is the base class of anything that follows this OOP mechanism. | |
-- Usage example: | |
-- | |
-- | |
-- CREATING A DERIVED CLASS | |
-- | |
-- local Human = Object:subclass { | |
-- name = "Anonymous", | |
-- weight = 0, | |
-- } | |
-- | |
-- function Human:say(text) | |
-- print(self.name .. " says: " .. text) | |
-- end | |
-- | |
-- | |
-- INSTANTIATING AN OBJECT | |
-- | |
-- local jim = Human { | |
-- name = "Jim Jimmalot" | |
-- weight = "85" | |
-- } | |
-- | |
-- jim:say("Hi!") | |
-- | |
-- | |
-- Default constructor, called when class is used as a function. | |
-- It turns 'instance' into an instance of 'class'. | |
-- | |
local function construct(class, instance) | |
assert(rawget(class, "__call"), "Trying to intantiate an instance") | |
setmetatable(instance, class) | |
instance:init() | |
return instance | |
end | |
-- | |
-- It turns 'subclass' into a subclass of 'class', and prepares it to be | |
-- used as metatable for instances of 'subclass'. | |
-- | |
local function subclass(class, subclass) | |
-- The class is reused as the metatable for subclasses and instances | |
subclass.__index = subclass | |
subclass.__call = construct | |
return setmetatable(subclass, class) | |
end | |
-- | |
-- Bootstrap the first class into the hierarchy | |
-- | |
local Object = subclass({ | |
-- Report an error when trying to access a member that doesn't exist | |
__index = function(table, key) | |
error("No such member: " .. tostring(key)) | |
end, | |
__call = construct, | |
}, {}) | |
Object.init = function() end | |
Object.subclass = subclass | |
-- | |
-- Returns a constructor for this class. This saves some overhead compared to | |
-- relying on the metatable, and can be used for classes that are frequently | |
-- instantiated. | |
-- | |
-- A module can only return the constructor, in which case the class can no | |
-- longer be subclassed and members are not accessible without creating an | |
-- instance. | |
-- | |
function Object:constructor() | |
local constructor = rawget(self, "_constructor_") | |
if not constructor then | |
assert(rawget(self, "__call"), "Instances can't have constructors") | |
local init = self.init | |
-- Avoid calling init when it's the empty default | |
if init == Object.init then | |
init = nil | |
end | |
constructor = function(instance) | |
setmetatable(instance, self) | |
if init then | |
init(instance) | |
end | |
return instance | |
end | |
self._constructor_ = constructor | |
end | |
return constructor | |
end | |
-- | |
-- Returns this object when it is an instance of the given class, and nil | |
-- otherwise. | |
-- | |
function Object:as(class) | |
local meta = getmetatable(self) | |
if type(class) == "table" then | |
while meta do | |
if meta == class then | |
return self | |
end | |
meta = getmetatable(meta) | |
end | |
elseif type(class) == "function" then | |
-- Assuming contructor function, so class can't be a subclass | |
if rawget(meta, "_constructor_") == class then | |
return self | |
end | |
end | |
return nil | |
end | |
-- | |
-- Convenience function to log a message from an instance. | |
-- | |
function Object:log(...) | |
print(tostring(self) .. ":", ...) | |
end | |
return Object |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local Object = require "object" | |
Object {} | |
local clock = os.clock | |
local Fruit = Object:subclass { | |
weight = 0, | |
color = "undefined", | |
} | |
function Fruit:eat() | |
self.weight = 0 | |
end | |
assert(rawget(getmetatable(Fruit), "__call") ~= nil, "Hmm") | |
-- An apple is a special kind of fruit | |
local Apple = Fruit:subclass { | |
weight = 1, | |
color = "green", | |
} | |
function Apple:eat() | |
-- Most people don't eat the whole apple | |
self.weight = self.weight / 2.0 | |
print("Ate apple") | |
end | |
-- A fruit basket that contains fruit | |
local FruitBasket = Object:subclass {} | |
function FruitBasket:init() | |
self.contents = {} | |
end | |
function FruitBasket:add(fruit) | |
self.contents[#self.contents + 1] = fruit | |
print("Added fruit with weight", fruit.weight, "and color", fruit.color) | |
end | |
-- Eating the fruit basket means eating all the fruit it contains | |
function FruitBasket:eat() | |
for k,v in pairs(self.contents) do | |
v:eat() | |
end | |
end | |
function FruitBasket:isEmpty() | |
return next(self.contents) == nil | |
end | |
function FruitBasket:weight() | |
local weight = 0 | |
for k,v in pairs(self.contents) do | |
weight = weight + v.weight | |
end | |
return weight | |
end | |
assert(rawget(getmetatable(FruitBasket), "__call") ~= nil, "Hmm2") | |
local fruitBasket = FruitBasket {} | |
for i=1,10 do | |
local apple = Apple {} | |
fruitBasket:add(apple) | |
end | |
print("Basket weight is", fruitBasket:weight()) | |
fruitBasket:eat() | |
print(fruitBasket:isEmpty() and "Basket empty" or "Basket not empty") | |
print("Basket weight is", fruitBasket:weight()) | |
local fruit = Fruit {} | |
print("Fruit as Fruit", fruit:as(Fruit)) | |
print("Fruit as Apple", fruit:as(Apple)) | |
fruit = Apple {} | |
print("Apple as Object", fruit:as(Object)) | |
print("Apple as Fruit", fruit:as(Fruit)) | |
print("Apple as Apple", fruit:as(Apple)) | |
local start | |
assert(Apple{}:as(Apple)) | |
Apple = Apple:constructor() | |
assert(Apple{}:as(Apple)) | |
start = clock() | |
for i=1,1000000 do | |
local fruit = Apple {} | |
end | |
print("Apple creation time:", clock() - start) | |
Point = Object:subclass { | |
x = 0, | |
y = 0, | |
} | |
start = clock() | |
for i=1,1000000 do | |
local point = Point { x = 10, y = 20 } | |
end | |
print("Point creation time:", clock() - start) | |
Point = Point:constructor() | |
start = clock() | |
for i=1,1000000 do | |
local point = Point { x = 10, y = 20 } | |
end | |
print("Point creation time (constructor):", clock() - start) | |
start = clock() | |
for i=1,1000000 do | |
local point = { x = 10, y = 20 } | |
end | |
print("Point creation time (raw):", clock() - start) | |
fruit = Fruit {} | |
local start = clock() | |
for i=1,1000000 do | |
fruit:eat() | |
end | |
print("Calling member function time:", clock() - start) | |
collectgarbage("collect") | |
print("Memory (base):", collectgarbage("count")) | |
local points = {} | |
for i=1,10000 do | |
points[i] = Point { x = 10, y = 20 } | |
end | |
collectgarbage("collect") | |
print("Memory (points):", collectgarbage("count")) | |
points = {} | |
for i=1,10000 do | |
points[i] = { x = 10, y = 20 } | |
end | |
collectgarbage("collect") | |
print("Memory (raw points):", collectgarbage("count")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment