I think a lot of programmers here use lua because of gmod and I see people trying to make lua object orientated all the time. The problem is there are dozens of ways of doing this and how do you know which way is the best? if there is one. In this post I want to share with you what I know and what I use in my projects.
Here is an example class:
[CODE]
-- Constructor params
return function(arg1, arg2)
local public = {}
-- An example of a private member
local x = 100
-- An example of a public member
public.name = "Billy"
-- An example of a private function
local function exampleFunc()
end
-- An example of a public accessor of a private variable.
function public.getX()
return x
end
return public
end
[/CODE]
Each class would have its own file.
How to use a class like this:
[CODE]
-- Get the constructor.
local constructor = require("exampleclass")
-- Make a new instance.
local instance = constructor(arg1, arg2)
-- Make a new instance quickly.
local instance2 = require("exampleclass")(arg1, arg2)
[/CODE]
At this point you might be iffy about all the requiring, I designed it like that so that I could have hundreds of classes in a project but not bog down the global table with a bunch of shit I'm only going to use in some places. I have found that the requiring it isn't a problem really in development but you could easily just throw the constructor into a global variable if it bugs you.
Now for polymorphism!
[CODE]
return function()
local public = require("supperclass")(args)
-- Overriding a supper public method.
local supperFoo = public.foo
function public.foo()
supperFoo()
-- Do more stuff here.
end
return public
end
[/CODE]
As you can see there is no additional class code needed just plain old lua behaving in a oop way. Most of the members of the class are local meaning that they are pretty darn fast, ideal for games. This solution is mostly complete, but there are some drawbacks. If you are making the class private (requiring it every time instead of making it's constructor global) then you can't easily make static members (local variables outside the class), as their values get overwritten ever time you require it. Another drawback is that each instance of the class has its own version of the functions. This could actually be seen as a pro and a con. It's bad because if you want to update the function of a class at run time you have to find every instance of the class and change the function. It's good because if your class it a button or something you can override one button's function without affecting the others.
I have used many different solutions before and this is the best I can come up with. I'd love to hear what y'all think and if you have any improvements or different solutions that you use.
Why not use metatables or something?
[QUOTE=Ott;41434813]Why not use metatables or something?[/QUOTE]
Good question!
Metatables can't have private methods/members, kind of important in huge projects like games. Metatable classes generally mean having global functions, having global functions means having to pass self as the first argument or using sugar syntax. Either way is not ideal.
Also you have have to write more boilerplate code with metatables.
You can still use metatables with my solution, they are just used to override operators.
[url]https://github.com/andrewmcwatters/lclass[/url]
[lua]-- inheritance.lua
-- simple class inheritance test
require( "class" )
-------------------------------------------------------------------------------
-- animal
-------------------------------------------------------------------------------
class "animal"
function animal:animal()
self.kingdom = "Animalia"
end
function animal:getKingdom()
return self.kingdom
end
local a = animal()
print( a:getKingdom() )
-------------------------------------------------------------------------------
-- amphibian
-- Base Class: animal
-------------------------------------------------------------------------------
class "amphibian" ( "animal" )
function amphibian:amphibian()
self.kingdom = "Animalia"
self.class = "Amphibia"
end
function amphibian:getClass()
return self.class
end
local a = amphibian()
print( a:getKingdom(), a:getClass() )
[/lua]
[editline]12th July 2013[/editline]
[lua]-- metatableinheritance.lua
-- metatable inheritance test
require( "class" )
-------------------------------------------------------------------------------
-- vehicle
-------------------------------------------------------------------------------
class "vehicle"
function vehicle:vehicle()
self.name = "none"
end
function vehicle:__tostring()
return "vehicle: " .. self.name
end
local v = vehicle()
print( v )
-------------------------------------------------------------------------------
-- car
-- Base Class: vehicle
-------------------------------------------------------------------------------
class "car" ( "vehicle" )
function car:car()
self.name = "car"
end
local c = car()
print( c )
[/lua]
Here's my setup:
[code]
Object = {}
-- Optional, it's just nice for convenience.
function Object:__call(...)
return self:new(...)
end
-- Constructor
function Object:__init()
end
-- Methods
function Object:parent()
return getmetatable(self).__index
end
function Object:new(...)
local o = {}
setmetatable(o, self)
self.__index = self
self.__call = Object.__call -- remove if you remove __call
o:__init(...)
return o
end
Object = Object:new()
[/code]
And here's the version with metamethod inheritence and pretty constructors:
[code]
Object = {}
-- Variables
Object.__metamethods = {
"__add", "__sub", "__mul", "__div", "__mod", "__pow", "__unm",
"__len", "__lt", "__le", "__concat", "__tostring"
}
-- Metamethods
function Object:__call(...)
return self:new(...)
end
function Object:__newindex(k, v)
if k == "init" or getfenv(2)[k] == self then
rawset(self, "__init", v)
else
rawset(self, k, v)
end
end
-- Constructor
function Object:__init()
end
-- Private/Static methods
function Object:__metamethod(event)
return function(...)
func = self[event]
if type(func) == "function" then
return func(...)
else
return func
end
end
end
-- Methods
function Object:parent()
return getmetatable(self).__index
end
function Object:new(...)
local o = {}
setmetatable(o, self)
self.__index = self
self.__call = Object.__call
self.__newindex = Object.__newindex
for k,v in pairs(Object.__metamethods) do
o[v] = self:__metamethod(v)
end
local err = o:__init(...)
return err or o
end
Object = Object:new()
[/code]
Unlike a lot of object systems I see in Lua, mine does not try to emulate a class OOP setup, it instead takes advantage of Lua's metatables and provides a very small and simple prototyping system. In my object system instantiation is derivation (sorry ACPM I lifted the example from you):
[code]
require "Object"
Animal = Object()
function Animal:Animal()
self.kingdom = "Animalia"
end
function Animal:getKingdom()
return self.kingdom
end
function Animal:getClass() -- This function shouldn never be called from base object Animal
return self.class
end
local a = Animal()
print(a:getKingdom()) -- "Animalia"
Amphibian = Animal()
function Amphibian:Amphibian()
Amphibian.class = "Amphibia"
end
---
local a = Amphibian()
print(a:getKingdom(), a:getClass()) -- "Animalia Amphibia"
[/code]
To derive an object from an existing one:
[code]
Amphibian = Animal ()
[/code]
At this point Amphibian is simply an instance of Animal, but then you modify Amphibian and create an instance of it. Every object can be a prototype for another.
Sorry, you need to Log In to post a reply to this thread.