• OOP Lua
    4 replies, posted
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.