[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: AKClassHierarchy
- From: Gavin Kistner <gavin@...>
- Date: Sat, 11 Feb 2006 11:25:44 -0700
At my work, I needed OOP in my Lua. I'm used to Ruby, so I created a
simple object/class framework that mimics it. In it, Classes are
objects that may have their own methods. To see the (complex, but
common-sense) inheritance rules, check out http://phrogz.net/
AnarkSamples/AKClassHierarchy.png
Features:
* Every instance of an object inherits from a common root
(AKObject.prototype).
* Supports classes with (single) inheritance.
* Instances of classes share properties/methods from a common space
(theClass.prototype.*).
* Shared properties for instances are in a separate space from
methods on the class itself.
* Every instance knows who its class is, and every class knows its
superclass.
* Classes are objects too - they inherit from AKClass.prototype,
which inherits from AKObject.prototype
* Every object (including classes) can easily have their own string
representation.
* Classes may define an 'initialize' function, which is executed on
instances when they are created.
* Because I use only __index tables, I assume it has a faster runtime
than solutions using __index functions.
Limitations
* No public/private methods/properties. Everything is public.
* Does not currently support any sort of 'super' mechanism for
magically calling methods from an ancestor with the same name.
* No 'mix-ins' or other forms of multi-inheritance.
I've included the code below, for feedback. At the end is a tiny
testcase showing how to use the framework to create classes and
instances.
--
************************************************************************
*********
AKObject = {
-- AKObject.new is used as the new() for each class,
-- and also to create AKClass itself
new = function( inAncestor, ... )
local theInstance = { }
setmetatable( theInstance, inAncestor.instancemeta )
-- If the class has an initialize() method defined,
-- run it with the instance as self, passing along arguments
-- supplied to new()
if type( inAncestor.initialize ) == 'function' then
inAncestor.initialize( theInstance, unpack( arg ) )
end
return theInstance
end
}
-- Root properties/methods shared by all instances (including Classes)
AKObject.prototype = { class = AKObject }
-- In case the class doesn't supply its own tostring() for instances
function AKObject.prototype:tostring( )
return "<"..tostring( self.class ).." instance>"
end
-- The __tostring metamethod always uses a 'tostring' property
function AKObject:tostring( ) return self:tostring( ) end
-- Class instances inherit from the AKObject.prototype
AKObject.instancemeta = { __index = AKObject.prototype, __tostring =
AKObject.tostring }
-- If prototype tables used their own tostring method,
-- they'd attempt to use the method meant for instances
AKObject.protometa = { __index = AKObject.prototype }
-- AKObject is, itself, an object.
-- Also give it a nice string representation
setmetatable( AKObject, {
__index = AKObject.prototype,
__tostring = function( ) return "AKObject" end
} )
-- Classes are objects too; they inherit methods from the AKObject
prototype
AKClass = AKObject:new( )
AKClass.prototype = { new=AKObject.new }
-- Class objects (that inherit from AKObject) go through AKClass's
prototype
AKObject.classmeta = { __index = AKClass.prototype, __tostring =
AKObject.tostring }
-- Class objects inherit through AKClass.prototype, and thence to
AKObject
setmetatable( AKClass.prototype, AKObject.instancemeta )
function AKClass:tostring( )
return "AKClass"
end
-- Classes use a special constructor that allows superclasses to be
specified
-- The superclass defaults to AKObject
-- Instances from AKClass:new() inherit from inSuperClass's prototype
-- Instances of a class's new() inherit from that class's prototype
function AKClass:new( inSuperClass )
-- If no superclass was supplied, this class inherits from AKObject
if not inSuperClass then inSuperClass = AKObject end
local theClass = {
class = self, -- The class of a class is AKClass
superclass = inSuperClass, -- A property pointing to the owner
prototype = { } -- Instances inherit from this
}
theClass.prototype.class = theClass
setmetatable( theClass.prototype, inSuperClass.protometa )
setmetatable( theClass, inSuperClass.classmeta )
-- Instances of this class inherit from its prototype
-- Subclass objects inherit from the class itself
theClass.instancemeta = { __index = theClass.prototype, __tostring =
AKObject.tostring }
theClass.classmeta = { __index = theClass, __tostring =
AKObject.tostring }
-- If prototype tables used their own tostring method,
-- they'd attempt to use the method meant for instances
theClass.protometa = { __index = theClass.prototype }
return theClass
end
--
************************************************************************
*********
Rectangle = AKClass:new( )
function Rectangle:tostring( ) return "Rectangle" end
function Rectangle:initialize( inW, inH )
self.w = inW or 0
self.h = inH or 0
end
function Rectangle.prototype:area( )
return self.w * self.h
end
function Rectangle.prototype:tostring( )
return string.format( '<%s w=%d h=%d>', tostring( self.class ),
self.w, self.h )
end
Square = AKClass:new( Rectangle )
function Square:tostring( ) return "Square" end
function Square:initialize( inLength )
Rectangle.initialize( self, inLength, inLength )
-- Using self.class.superclass.initialize would break inheritance
end
UnitSquare = AKClass:new( Square )
function UnitSquare:initialize( )
Square.initialize( self, 1 )
-- Using self.class.superclass.initialize would break inheritance
end
print( AKObject )
print( AKClass )
print( Rectangle )
print( Square )
print( UnitSquare )
assert( tostring( AKObject ) == 'AKObject' )
assert( tostring( AKClass ) == 'AKClass' )
assert( tostring( Rectangle ) == 'Rectangle' )
assert( tostring( Square ) == 'Square' )
assert( tostring( UnitSquare ) == 'Square' )
r = Rectangle:new( 10, 20 )
print( r )
assert( tostring( r ) == '<Rectangle w=10 h=20>' )
assert( r:area( ) == 200 )
s = Square:new( 5 )
print( s )
assert( tostring( s ) == '<Square w=5 h=5>' )
assert( s:area( ) == 25 )
u = UnitSquare:new( 999 )
print( u )
assert( tostring( u ) == '<Square w=1 h=1>' )
assert( u:area( ) == 1 )
DeadClass = AKClass:new( )
print( DeadClass )
assert( DeadClass.superclass == AKObject )
assert( tostring( DeadClass ) == '<AKClass instance>' )