batteries/class.lua
Max Cahill 3cc177a0c0 BREAKING class interface refactor - all classes will need a minor update, see class.lua
tl;dr is that new no longer needs to call init, calling :new() directly in user code is not allowed, properties are copied, metamethods work, and a config table is needed rather than a class to extend from, so use {extends = superclass} if you want a minimal fix
2021-07-15 16:15:27 +10:00

136 lines
3.0 KiB
Lua

--[[
barebones oop basics
call the class object to construct a new instance
classes are used as metatables directly so that
metamethods "just work" - except for index, which is
used to hook up instance methods
classes do use a prototype chain for inheritance, but
also copy their interfaces (including superclass)
we copy interfaces in classes rather than relying on
a prototype chain, so that pairs on the class gets
all the methods when implemented as an interface
class properties are not copied and should likely
be accessed through the concrete class object so
that everything refers to the same object
arguments (all optional):
name (string):
the name to use for type()
extends (class):
superclass for basic inheritance
implements (ordered table of classes):
mixins/interfaces
default_tostring (boolean):
whether or not to provide a default tostring function
]]
--generate unique increasing class ids
local class_id_gen = 0
local function next_class_id()
class_id_gen = class_id_gen + 1
return class_id_gen
end
--implement an interface into c
local function implement(c, interface)
c.__is[interface] = true
for k, v in pairs(interface) do
if c[k] == nil and type(v) == "function" then
c[k] = v
end
end
end
--build a new class
local function class(config)
local class_id = next_class_id()
config = config or {}
local extends = config.extends
local implements = config.implements
local name = config.name or ("unnamed class %d)"):format(class_id)
local c = {}
--unique generated id per-class
c.__id = class_id
--the class name
c.__name = name
--prototype
c.__index = c
--return the name of the class
function c:type()
return name
end
if config.default_tostring then
function c:__tostring()
return name
end
end
--class metatable to set up constructor call
setmetatable(c, {
__call = function(self, ...)
local instance = setmetatable({}, self)
instance:new(...)
return instance
end,
__index = extends,
})
--checking class membership for probably-too-dynamic code
--returns true for both extended classes and implemented interfaces
--(implemented with a hashset for fast lookups)
c.__is = {}
c.__is[c] = true
function c:is(t)
return self.__is[t] == true
end
--get the inherited class for super calls if/as needed
--allows overrides that still refer to superclass behaviour
c.__super = extends
--nop by default
function c:super() end
if c.__super then
--perform a super construction for an instance
function c:super(...)
c.__super.new(self, ...)
end
--implement superclass interface
implement(c, c.__super)
end
--implement all the passed interfaces/mixins
--in order provided
if implements then
for _, interface in ipairs(implements) do
implement(c, interface)
end
end
--default constructor, just proxy to the super constructor
--override it and use to set up the properties of the instance
--but don't forget to call the super constructor!
function c:new(...)
self:super(...)
end
--done
return c
end
return class