├── oo.lua ├── oo.md └── oo_test.lua /oo.lua: -------------------------------------------------------------------------------- 1 | 2 | --object system with virtual properties and method overriding hooks. 3 | --Written by Cosmin Apreutesei. Public Domain. 4 | 5 | if not ... then require'oo_test'; return end 6 | 7 | local Object = {classname = 'Object'} 8 | 9 | local function class(super,...) 10 | return (super or Object):subclass(...) 11 | end 12 | 13 | local function isfunc(test) 14 | return function(obj, class) 15 | if type(obj) ~= 'table' then return false end 16 | local test = obj[test] 17 | if type(test) ~= 'function' then return false end 18 | return test(obj, class) 19 | end 20 | end 21 | local is = isfunc'is' 22 | local isinstance = isfunc'isinstance' 23 | 24 | function Object:subclass(classname, overrides) 25 | local subclass = {} 26 | subclass.super = self 27 | subclass.classname = classname or '' 28 | subclass.isclass = true 29 | if classname then 30 | subclass['is'..classname] = true 31 | end 32 | setmetatable(subclass, getmetatable(self)) 33 | if overrides then 34 | for k,v in pairs(overrides) do 35 | subclass[k] = v 36 | end 37 | end 38 | return subclass 39 | end 40 | 41 | function Object:init(...) return ... end 42 | 43 | function Object:create(...) 44 | local o = setmetatable({super = self}, getmetatable(self)) 45 | o:init(...) 46 | return o 47 | end 48 | 49 | local meta = {} 50 | 51 | function meta.__call(o,...) 52 | return o:create(...) 53 | end 54 | 55 | --note: this is the perf. bottleneck of the entire module. 56 | function meta:__index(k) 57 | if type(k) == 'string' then 58 | --some keys are not virtualizable to avoid infinite recursion, 59 | --but they are dynamically inheritable nonetheless. 60 | if k ~= '__getters' and k ~= '__setters' then 61 | if k == 'super' then --'super' is not even inheritable 62 | return nil 63 | end 64 | local isinstance = rawget(self, 'isclass') == nil 65 | local getters = isinstance and self.__getters 66 | local get = getters and getters[k] 67 | if get then --virtual property 68 | return get(self, k) 69 | end 70 | end 71 | end 72 | local super = rawget(self, 'super') 73 | return super and super[k] --inherited property 74 | end 75 | 76 | --create a table in t[k] that inherits dynamically from t.super[k], 77 | --recursively creating all the t.super[k] tables if they're missing. 78 | local function create_table(t, k) 79 | local v = rawget(t, k) 80 | if v ~= nil then 81 | return v 82 | end 83 | v = {} 84 | rawset(t, k, v) 85 | setmetatable(v, v) 86 | local super = rawget(t, 'super') 87 | if super then 88 | v.__index = create_table(super, k) 89 | end 90 | return v 91 | end 92 | 93 | --This check is to allow the optimization of copying __setters and __getters 94 | --on the instance (see tests) which, if applied, then overriding getters and 95 | --setters through the instance is not allowed anymore, since that would not 96 | --patch the instance, but it would patch the class instead. 97 | --TODO: find a way to remove this limitation in order to allow overriding 98 | --of getters/setters on instances (no use case for it yet). 99 | local function check_not_instance(self) 100 | local isinstance = rawget(self, 'isclass') == nil 101 | assert(not isinstance, 'NYI: trying to define a getter/setter on an instance.') 102 | end 103 | 104 | function meta:__newindex(k,v) 105 | if type(k) ~= 'string' then 106 | rawset(self, k, v) 107 | return 108 | end 109 | local isinstance = rawget(self, 'isclass') == nil 110 | local setters = isinstance and self.__setters 111 | local set = setters and setters[k] 112 | if set then --r/w property 113 | set(self, v) 114 | return 115 | end 116 | local getters = isinstance and self.__getters 117 | if getters and getters[k] then --read-only property 118 | error(string.format('property "%s" is read/only', k)) 119 | end 120 | if k:find'^get_' or k:find'^set_' then --install getter or setter 121 | check_not_instance(self) 122 | local name = k:sub(5) 123 | local tname = k:find'^get_' and '__getters' or '__setters' 124 | create_table(self, tname)[name] = v 125 | elseif k:find'^before_' then --install before hook 126 | local method_name = k:match'^before_(.*)' 127 | self:before(method_name, v) 128 | elseif k:find'^after_' then --install after hook 129 | local method_name = k:match'^after_(.*)' 130 | self:after(method_name, v) 131 | elseif k:find'^override_' then --install override hook 132 | local method_name = k:match'^override_(.*)' 133 | self:override(method_name, v) 134 | else 135 | rawset(self, k, v) 136 | end 137 | end 138 | 139 | local function install(self, combine, method_name, hook) 140 | if method_name:find'^get_' then 141 | check_not_instance(self) 142 | local prop = method_name:sub(5) 143 | local method = combine(self.__getters and self.__getters[prop], hook) 144 | self[method_name] = method 145 | elseif method_name:find'^set_' then 146 | check_not_instance(self) 147 | local prop = method_name:sub(5) 148 | local method = combine(self.__setters and self.__setters[prop], hook) 149 | self[method_name] = method 150 | else 151 | rawset(self, method_name, combine(self[method_name], hook)) 152 | end 153 | end 154 | 155 | local function before(method, hook) 156 | if method then 157 | return function(self, ...) 158 | hook(self, ...) 159 | return method(self, ...) 160 | end 161 | else 162 | return hook 163 | end 164 | end 165 | function Object:before(method_name, hook) 166 | install(self, before, method_name, hook) 167 | end 168 | 169 | local function after(method, hook) 170 | if method then 171 | return function(self, ...) 172 | method(self, ...) 173 | return hook(self, ...) 174 | end 175 | else 176 | return hook 177 | end 178 | end 179 | function Object:after(method_name, hook) 180 | install(self, after, method_name, hook) 181 | end 182 | 183 | local function noop() return end 184 | local function override(method, hook) 185 | local method = method or noop 186 | return function(self, ...) 187 | return hook(self, method, ...) 188 | end 189 | end 190 | function Object:override(method_name, hook) 191 | install(self, override, method_name, hook) 192 | end 193 | 194 | function Object:is(class) 195 | assert(type(class) == 'table' or type(class) == 'string') 196 | local super = rawget(self, 'super') 197 | if super == class or self == class or self.classname == class then 198 | return true 199 | elseif super then 200 | return super:is(class) 201 | else 202 | return false 203 | end 204 | end 205 | 206 | function Object:hasproperty(k) 207 | if rawget(self, k) ~= nil then return true, 'field' end 208 | if type(k) == 'string' and k ~= '__getters' and k ~= '__setters' then 209 | if k == 'super' then return false end 210 | local getters = self.__getters 211 | local get = getters and getters[k] 212 | if get then return true, 'property' end 213 | local setters = self.__setters 214 | local set = setters and setters[k] 215 | if set then return true, 'property' end 216 | end 217 | local super = rawget(self, 'super') 218 | if not super then return false end 219 | return super:hasproperty(k) 220 | end 221 | 222 | function Object:isinstance(class) 223 | return rawget(self, 'isclass') == nil and (not class or self:is(class)) 224 | end 225 | 226 | --closest ancestor that `other` has in self's hierarchy. 227 | local function closest_ancestor(self, other) 228 | while not is(other, self) do 229 | self = rawget(self, 'super') 230 | if not self then 231 | return nil --other is not an Object 232 | end 233 | end 234 | return self 235 | end 236 | 237 | --returns iterator; iterates bottom-up in the inheritance chain 238 | function Object:allpairs(stop_super) 239 | local source = self 240 | if source == stop_super then 241 | return function() return nil end 242 | end 243 | local k,v 244 | return function() 245 | k,v = next(source,k) 246 | if k == nil then 247 | source = rawget(source, 'super') 248 | if source == nil then return nil end 249 | if source == stop_super then return nil end 250 | k,v = next(source) 251 | end 252 | return k,v,source 253 | end 254 | end 255 | 256 | local function copy_table(dst, src, k, override) 257 | create_table(dst, k) 258 | local st = rawget(src, k) 259 | if st then 260 | local dt = rawget(dst, k) 261 | if dt == nil then 262 | dt = {} 263 | rawset(dst, k, dt) 264 | end 265 | for k,v in pairs(st) do 266 | if override or rawget(dt, k) == nil then 267 | rawset(dt, k, v) 268 | end 269 | end 270 | else 271 | local super = rawget(src, 'super') 272 | if super then 273 | return copy_table(dst, super, k) 274 | end 275 | end 276 | end 277 | 278 | function Object:inherit(other, override, stop_super) 279 | if other and not is(other, Object) then --plain table, treat as mixin 280 | for k,v in pairs(other) do 281 | if override or not self:hasproperty(k) then 282 | self[k] = v --not rawsetting so that meta-methods apply 283 | end 284 | end 285 | else --oo class or instance 286 | if other and not is(self, other) then --mixin 287 | --prevent inheriting fields from common ancestors by default! 288 | if stop_super == nil then --pass false to disable this filter. 289 | stop_super = closest_ancestor(self, other) 290 | end 291 | else --superclass 292 | other = rawget(self, 'super') 293 | end 294 | local properties = {} 295 | for k,v in other:allpairs(stop_super) do 296 | if properties[k] == nil then 297 | properties[k] = v 298 | end 299 | end 300 | local isclass = rawget(self, 'isclass') 301 | for k,v in pairs(properties) do 302 | if (override or rawget(self, k) == nil) 303 | and (k ~= 'isclass' or isclass) --set `isclass` if it's a class 304 | and k ~= 'classname' --keep the classname (preserve identity) 305 | and k ~= 'super' --keep super (preserve dynamic inheritance) 306 | and k ~= '__getters' --getters are deep-copied 307 | and k ~= '__setters' --getters are deep-copied 308 | then 309 | rawset(self, k, v) 310 | end 311 | end 312 | --copy getters and setters 313 | copy_table(self, other, '__getters', override) 314 | copy_table(self, other, '__setters', override) 315 | end 316 | 317 | --copy metafields if metatables are different 318 | local src_meta = getmetatable(other) 319 | local dst_meta = getmetatable(self) 320 | if src_meta and src_meta ~= dst_meta then 321 | for k,v in pairs(src_meta) do 322 | if override or rawget(dst_meta, k) == nil then 323 | rawset(dst_meta, k, v) 324 | end 325 | end 326 | end 327 | return self 328 | end 329 | 330 | function Object:detach() 331 | self:inherit() 332 | self.classname = self.classname --store the classname 333 | rawset(self, 'super', nil) 334 | return self 335 | end 336 | 337 | function Object:gen_properties(names, getter, setter) 338 | for k in pairs(names) do 339 | if getter then 340 | self['get_'..k] = function(self) return getter(self, k) end 341 | end 342 | if setter then 343 | self['set_'..k] = function(self, v) return setter(self, k, v) end 344 | end 345 | end 346 | end 347 | 348 | --debugging 349 | 350 | local function pad(s, n) return s..(' '):rep(n - #s) end 351 | 352 | local props_conv = {g = 'r', s = 'w', gs = 'rw', sg = 'rw'} 353 | local oo_state_fields = {super=1, __getters=1, __setters=1, __observers=1} 354 | 355 | function Object:inspect(show_oo) 356 | local sortedpairs = require'glue'.sortedpairs 357 | --collect data 358 | local supers = {} --{super1,...} 359 | local keys = {} --{super = {key1 = true,...}} 360 | local props = {} --{super = {prop1 = true,...}} 361 | local sources = {} --{key = source} 362 | local source, keys_t, props_t 363 | for k,v,src in self:allpairs() do 364 | if sources[k] == nil then sources[k] = src end 365 | if src ~= source then 366 | source = src 367 | keys_t = {} 368 | props_t = {} 369 | keys[source] = keys_t 370 | props[source] = props_t 371 | supers[#supers+1] = source 372 | end 373 | if sources[k] == src then 374 | keys_t[k] = true 375 | end 376 | end 377 | if self.__getters then 378 | for prop in pairs(self.__getters) do 379 | if prop ~= '__index' then 380 | props_t[prop] = 'g' 381 | end 382 | end 383 | end 384 | if self.__setters then 385 | for prop in pairs(self.__setters) do 386 | if prop ~= '__index' then 387 | props_t[prop] = (props_t[prop] or '')..'s' 388 | end 389 | end 390 | end 391 | 392 | --print values 393 | for i,super in ipairs(supers) do 394 | if show_oo or super ~= Object then 395 | print('from '..( 396 | super == self and 397 | ('self'..(super.classname ~= '' 398 | and ' ('..super.classname..')' or '')) 399 | or 'super #'..tostring(i-1)..(super.classname ~= '' 400 | and ' ('..super.classname..')' or '') 401 | )..':') 402 | for k,v in sortedpairs(props[super]) do 403 | print(' '..pad(k..' ('..props_conv[v]..')', 16), 404 | tostring(super[k])) 405 | end 406 | for k in sortedpairs(keys[super]) do 407 | local oo = oo_state_fields[k] or Object[k] ~= nil 408 | if show_oo or not oo then 409 | print(' '..(oo and '* ' or ' ')..pad(k, 16), 410 | tostring(super[k])) 411 | end 412 | end 413 | end 414 | end 415 | end 416 | 417 | setmetatable(Object, meta) 418 | 419 | return setmetatable({ 420 | class = class, 421 | is = is, 422 | isinstance = isinstance, 423 | closest_ancestor = closest_ancestor, 424 | Object = Object, 425 | }, { 426 | __index = function(t,k) 427 | return function(super, ...) 428 | if type(super) == 'string' then 429 | super = t[super] 430 | end 431 | local cls = class(super, ...) 432 | cls.classname = k 433 | t[k] = cls 434 | return cls 435 | end 436 | end 437 | }) 438 | -------------------------------------------------------------------------------- /oo.md: -------------------------------------------------------------------------------- 1 | --- 2 | tagline: fancy object system 3 | --- 4 | 5 | ## `local oo = require'oo'` 6 | 7 | Object system with virtual properties and method overriding hooks. 8 | 9 | ## In a nutshell 10 | 11 | * single, dynamic inheritance by default: 12 | * `Fruit = oo.Fruit()` 13 | * `Apple = oo.Apple(Fruit, {carpels = 5})` 14 | * `Apple.carpels -> 5` (class field) 15 | * `apple = Apple(...)` 16 | * `apple.carpels -> 5` (serves as default value) 17 | * `apple.super -> Apple` 18 | * `Apple.super -> Fruit` 19 | * `apple.isApple, apple.isFruit, Apple.isApple, Apple.isFruit -> true` 20 | * multiple, static inheritance by request: 21 | * `Apple:inherit(Fruit[,replace])` - statically inherit `Fruit`, 22 | optionally replacing existing properties. 23 | * `Apple:detach()` - detach from the parent class, in other words 24 | statically inherit `self.super`. 25 | * virtual properties with getter and setter: 26 | * reading `apple.foo` calls `Apple:get_foo()` to get the value, if 27 | `apple.get_foo` is defined. 28 | * assignment to `apple.foo` calls `Apple:set_foo(value)` if 29 | `Apple.set_foo` is defined. 30 | * missing the setter, the property is considered read-only and the 31 | assignment fails. 32 | * method overriding hooks: 33 | * `function Apple:before_pick(args...) end` makes `apple:pick()` call the 34 | code inside `before_pick()` first. 35 | * `function Apple:after_pick(args...) end` makes `apple:pick()` call the 36 | code inside `after_pick()` last. 37 | * `function Apple:override_pick(inherited, ...) end` lets you override 38 | `Apple:pick()` and call `inherited(self, ...)`. 39 | * virtual classes (aka dependency injection, described below). 40 | * introspection: 41 | * `oo.is(obj|class, class|classname) -> true|false` - check instance/class ancestry 42 | * `oo.isinstance(obj|class[, class|classname]) -> true|false` - check instance ancestry 43 | * `apple:is(class|classname) -> true|false` - check instance/class ancestry 44 | * `apple:isinstance([class|classname]) -> true|false` - check instance ancestry 45 | * `oo.closest_ancestor(apple, orange) -> Fruit` - closest ancestor of `orange` in 46 | `apple`'s hierarchy 47 | * `apple:hasproperty(name) -> false | true, 'field'|'property' - check if property 48 | exists without accessing its value 49 | * `self:allpairs([super]) -> iterator() -> name, value, source` - iterate all 50 | properties, including inherited _and overriden_ ones up until `super`. 51 | * `self:allproperties([super])` -> get a table of all current properties and values, 52 | including inherited ones up until `super`. 53 | * `self:inspect([show_oo_fields])` - inspect the class/instance structure 54 | and contents in detail (requires [glue]). 55 | * overridable subclassing and instantiation mechanisms: 56 | * `Fruit = oo.Fruit()` is sugar for `Fruit = oo.Object:subclass('Fruit')` 57 | * `Apple = oo.Apple(Fruit)` is sugar for `Apple = Fruit:subclass('Apple')` 58 | * `apple = Apple(...)` is sugar for `apple = Apple:create(...)` 59 | * `Apple:create()` calls `apple:init(...)` 60 | 61 | ## Inheritance and instantiation 62 | 63 | **Classes are created** with `oo.ClassName([super])`, where `super` is 64 | usually another class, but can also be an instance, which is useful for 65 | creating polymorphic "views" on existing instances. 66 | 67 | ~~~{.lua} 68 | local Fruit = oo.Fruit() 69 | ~~~ 70 | 71 | You can also create anonymous classes with `oo.class([super])`: 72 | 73 | ~~~{.lua} 74 | local cls = oo.class() 75 | ~~~ 76 | 77 | **Instances are created** with `cls:create(...)` or simply `cls()`, which in 78 | turn calls `cls:init(...)` which is the object constructor. While `cls` is 79 | normally a class, it can also be an instance, which effectively enables 80 | prototype-based inheritance. 81 | 82 | ~~~{.lua} 83 | local obj = cls() 84 | ~~~ 85 | 86 | **The superclass** of a class or the class of an instance is accessible as 87 | `self.super`. 88 | 89 | ~~~{.lua} 90 | assert(obj.super == cls) 91 | assert(cls.super == oo.Object) 92 | ~~~ 93 | 94 | **Inheritance is dynamic**: properties are looked up at runtime in 95 | `self.super` and changing a property or method in the superclass reflects on 96 | all subclasses and instances. This can be slow, but it saves space. 97 | 98 | ~~~{.lua} 99 | cls.the_answer = 42 100 | assert(obj.the_answer == 42) 101 | ~~~ 102 | 103 | **You can detach** the class/instance from its parent class by calling 104 | `self:detach() -> self`. This copies all inherited fields to the 105 | class/instance and removes `self.super`. 106 | 107 | ~~~{.lua} 108 | cls:detach() 109 | obj:detach() 110 | assert(obj.super == nil) 111 | assert(cls.super == nil) 112 | assert(cls.the_answer == 42) 113 | assert(obj.the_answer == 42) 114 | ~~~ 115 | 116 | **Static inheritance** can be achieved by calling 117 | `self:inherit([other],[replace],[stop_super]) -> self` which copies over 118 | the properties of another class or instance, effectively *monkey-patching* 119 | `self`, optionally overriding properties with the same name. The fields 120 | `self.classname` and `self.super` are always preserved though, even with 121 | the `override` flag. 122 | 123 | * `other` can also be a plain table, in which case it is shallow-copied. 124 | * `other` defaults to `self.super`. 125 | * `stop_super` limits how far up in the inheritance chain of `other` 126 | too look for fields and properties to copy. 127 | * if `other` is not in `self`'s hierarchy, `stop_super` defaults to 128 | `oo.closest_ancestor(self, other)` in order to prevent inheriting any fields 129 | from common ancestors, which would undo any overridings done in subclasses 130 | of the closest ancestor. 131 | 132 | ~~~{.lua} 133 | local other_cls = oo.class() 134 | other_cls.the_answer = 13 135 | 136 | obj:inherit(other_cls) 137 | assert(obj.the_answer == 13) --obj continues to dynamically inherit cls.the_answer 138 | --but statically inherited other_cls.the_answer 139 | 140 | obj.the_answer = nil 141 | assert(obj.the_answer == 42) --reverted to class default 142 | 143 | cls:inherit(other_cls) 144 | assert(cls.the_answer == 42) --no override 145 | 146 | cls:inherit(other_cls, true) 147 | assert(cls.the_answer == 13) --override 148 | ~~~ 149 | 150 | In fact, `self:detach()` is written as `self:inherit(self.super)` with the 151 | minor detail of setting `self.classname = self.classname` and removing 152 | `self.super`. 153 | 154 | __NOTE:__ Detaching instances _or final classes_ helps preventing LuaJIT from 155 | bailing out to the interpreter which can result in 100x performance drop. 156 | Even in interpreter mode, detaching instances can increase performance for 157 | method lookup by 10x (see benchmarks). 158 | 159 | You can do this easily with: 160 | 161 | ~~~{.lua} 162 | --detach instances of (subclasses of) myclass from their class. 163 | --patching myclass or its subclasses afterwards will not affect 164 | --existing instances but it will affect new instnaces. 165 | function myclass:before_init() 166 | self:detach() 167 | end 168 | 169 | --detach all new subclasses of myclass. patching myclass or its 170 | --supers afterwards will have no effect on existing subclasses 171 | --of myclass or its instances. patching final classes though 172 | --will affect both new and existing instances. 173 | function myclass:override_subclass(inherited, ...) 174 | return inherited(self, ...):detach() 175 | end 176 | ~~~ 177 | 178 | __NOTE:__ Static inheritance changes field lookup semantics in a subtle way: 179 | because field values no longer dynamically overshadow the values set in the 180 | superclasses, setting a statically inherited field to `nil` doesn't expose 181 | back the value from the super class, instead the field remains `nil`. 182 | 183 | To further customize how the values are copied over for static inheritance, 184 | override `self:properties()`. 185 | 186 | ## Virtual properties 187 | 188 | **Virtual properties** are created by defining a getter and a setter. Once 189 | you have defined `self:get_foo()` and `self:set_foo(value)` you can read and 190 | write to `self.foo` and the getter and setter will be called instead. 191 | The setter is optional. Assigning a value to a property that doesn't have 192 | a setter results in an error. 193 | 194 | Getters and setters are only called on instances. This allows setting default 195 | values for properties on the class as plain fields with the same name as the 196 | property, following that those defaults will be applied manually in the 197 | constructor with `self.foo = self.super.foo`. 198 | 199 | There are no virtual properties for classes. Use singleton instances instead. 200 | 201 | ~~~{.lua} 202 | function cls:get_answer_to_life() return deep_thought:get_answer() end 203 | function cls:set_answer_to_life(v) deep_thought:set_answer(v) end 204 | obj = cls() 205 | obj.answer_to_life = 42 206 | assert(obj.answer_to_life == 42) --assuming deep_thought can store a number 207 | ~~~ 208 | 209 | Virtual properties can be *generated in bulk* given a _multikey_ getter and 210 | a _multikey_ setter and a list of property names, by calling 211 | `self:gen_properties(names, [getter], [setter])`. The setter and getter must 212 | be methods of form: 213 | 214 | * `getter(self, k) -> v` 215 | * `setter(self, k, v)` 216 | 217 | ## Overriding hooks 218 | 219 | Overriding hooks are sugar to make method overriding more easy and readable. 220 | 221 | Instead of: 222 | 223 | ~~~{.lua} 224 | function Apple:pick(arg) 225 | print('picking', arg) 226 | local ret = Apple.super.pick(self, arg) 227 | print('picked', ret) 228 | return ret 229 | end 230 | ~~~ 231 | 232 | Write: 233 | 234 | ~~~{.lua} 235 | function Apple:override_pick(inherited, arg, ...) 236 | print('picking', arg) 237 | local ret = inherited(self, arg, ...) 238 | print('picked', ret) 239 | return ret 240 | end 241 | ~~~ 242 | 243 | Or even better: 244 | 245 | ~~~{.lua} 246 | function Apple:before_pick(arg) 247 | print('picking', arg) 248 | end 249 | 250 | function Apple:after_pick(arg) 251 | print('picked', arg) 252 | return ret 253 | end 254 | 255 | ~~~ 256 | 257 | By defining `self:before_(...)` a new implementation for 258 | `self.` is created which calls the before hook and then calls the 259 | existing (inherited) implementation. Both calls receive all arguments. 260 | 261 | By defining `self:after_(...)` a new implementation for 262 | `self.` is created which calls the existing (inherited) 263 | implementation, after which it calls the hook and returns whatever the hook 264 | returns. Both calls receive all arguments. 265 | 266 | By defining `self:override_(inherited, ...)` you can access 267 | `self.super.` as `inherited`. 268 | 269 | ~~~{.lua} 270 | function cls:before_init(foo, bar) 271 | self.foo = foo or default_foo 272 | self.bar = bar or default_bar 273 | end 274 | 275 | function cls:after_init() 276 | --allocate resources 277 | end 278 | 279 | function cls:before_destroy() 280 | --destroy resources 281 | end 282 | ~~~ 283 | 284 | If you don't know the name of the method you want to override until runtime, 285 | use `cls:before(name, func)`, `cls:after(name, func)` and 286 | `cls:override(name, func)` instead. 287 | 288 | ## Virtual classes (aka dependency injection) 289 | 290 | Virtual classes provide an additional way to extend composite objects 291 | (objects which need to instantiate other objects) beyond inheritance which 292 | doesn't by itself cover extending the classes of the sub-objects of the 293 | composite object. Virtual classes come for free in languages where classes 294 | are first-class entitites: all you have to do is to make the inner class 295 | a class field of the outer class and instantiate it with `self:inner_class()`. 296 | This simple indirection has many advantages: 297 | 298 | * it allows subclassing the inner class in subclasses of the outer class 299 | by just overriding the `inner_class` field. 300 | * using `self:inner_class()` instead of `self.inner_class()` passes the 301 | outer object as the second arg to the constructor of the inner object 302 | (the first arg is the inner object) so that you can reference the outer 303 | object in the constructor, which is usually needed. 304 | * the`inner_class` field can be used as a method of the outer class so 305 | it can be made part of its public API without needing any additional 306 | wrapping, and it can also be overriden with a normal method in subclasses 307 | of the outer class (the overriding mechanism still works even if it's 308 | not overriding a real method). 309 | 310 | ## Events 311 | 312 | Events are useful for associating actions with callback functions. This 313 | can already be done more flexibly with plain methods and overriding, but 314 | events have the distinct ability to revert the overidding at runtime 315 | (with `obj:off()`). They also differ in the fact that returning a non-nil 316 | value from a callback short-circuits the call chain and the value is 317 | returned back to the user. 318 | 319 | The events functionality can be enabled by adding the [events] mixin to 320 | oo's base class (or to any other class): 321 | 322 | ~~~{.lua} 323 | local events = require'events' 324 | oo.Object:inherit(events) 325 | ~~~ 326 | 327 | ## Performance Tips 328 | 329 | Instance fields are accessed directly but methods and default values 330 | (class fields) go through a slower dynamic dispatch function (it's the 331 | price you pay for virtual properties). Copying class fields to the instance 332 | by calling `self:inherit()` will short-circuit this lookup at the expense 333 | of more memory consumption. Fields with a `nil` value go through the same 334 | function too so providing a `false` default value to those fields will 335 | also speed up their lookup. 336 | -------------------------------------------------------------------------------- /oo_test.lua: -------------------------------------------------------------------------------- 1 | local oo = require'oo' 2 | local clock = require'time'.clock 3 | 4 | --inheritance 5 | local c1 = oo.class() 6 | c1.classname = 'c1' 7 | c1.a = 1 8 | c1.b = 1 9 | local c2 = oo.class(c1) 10 | c2.classname = 'c2' 11 | c2.b = 2 12 | c2.c = 2 13 | assert(c2.super == c1) 14 | assert(c2.unknown == nil) 15 | assert(c2.a == 1) 16 | assert(c2.b == 2) 17 | assert(c2.c == 2) 18 | assert(c2.init == c1.init) 19 | 20 | --polymorphism 21 | function c1:before_init(...) 22 | print('c1 before_init',...) 23 | self.b = ... 24 | assert(self.b == 'o') 25 | return self.b 26 | end 27 | function c1:after_init() print('c1 after_init') end 28 | function c2:before_init(...) print('c2 before_init',...); return ... end 29 | function c2:after_init() print('c2 after_init') end 30 | function c2:override_init(inherited, ...) 31 | print('c2 overriden init', ...) 32 | return inherited(self, ...) 33 | end 34 | assert(c2.init ~= c1.init) 35 | local o = c2('o') 36 | assert(o.a == 1) 37 | assert(o.b == 'o') 38 | assert(o.c == 2) 39 | assert(o.super == c2) 40 | assert(o.unknown == nil) 41 | 42 | assert(o:is'c1') 43 | assert(o:is'c2') 44 | assert(o:is(o)) 45 | assert(c2:is(c1)) 46 | assert(c1:is(c1)) 47 | assert(c1:is'c1') 48 | assert(o:is'o' == false) 49 | assert(oo.Object:is(oo.Object)) 50 | 51 | local o2 = c1('o') 52 | assert(oo.closest_ancestor(c1, c2) == c1) --subject is target's super 53 | assert(oo.closest_ancestor(o, o2) == c1) --target's super 54 | assert(oo.closest_ancestor(o2, o) == c1) --subject's super 55 | assert(oo.closest_ancestor(c1, c2) == c1) --subject 56 | assert(oo.closest_ancestor(o2, c1) == c1) --target 57 | assert(oo.closest_ancestor(o2, oo.Object) == oo.Object) --subject's super, root 58 | assert(oo.closest_ancestor(oo.Object, oo.Object) == oo.Object) --root 59 | 60 | --arg passing through hooks 61 | local t = {} 62 | function c1:test_args(x, y) t[#t+1] = 'test'; assert(x + y == 5) end 63 | function c2:before_test_args(x, y) t[#t+1] = 'before'; assert(x + y == 5) end 64 | function c2:after_test_args(x, y) t[#t+1] = 'after'; return x + y end 65 | function c2:override_test_args(inherited, x, y) 66 | t[#t+1] = 'override1' 67 | assert(inherited(self, x, y) == x + y) 68 | t[#t+1] = 'override2' 69 | return x + y + 1 70 | end 71 | assert(o:test_args(2, 3) == 2 + 3 + 1) 72 | assert(#t == 5) 73 | assert(t[1] == 'override1') 74 | assert(t[2] == 'before') 75 | assert(t[3] == 'test') 76 | assert(t[4] == 'after') 77 | assert(t[5] == 'override2') 78 | 79 | --virtual properties 80 | local getter_called, setter_called 81 | function c2:get_x() getter_called = true; return self.__x end 82 | function c2:set_x(x) setter_called = true; self.__x = x end 83 | o.x = 42 84 | assert(setter_called) 85 | assert(o.x == 42) 86 | assert(getter_called) 87 | 88 | --stored properties 89 | --function o:set_s(s) print('set_s', s) assert(s == 13) end 90 | --o.s = 13 91 | --assert(o.s == 13) 92 | 93 | --virtual properties and inheritance 94 | local getter_called, setter_called 95 | function c1:get_c1x() getter_called = true; return self.__c1x end 96 | function c1:set_c1x(x) setter_called = true; self.__c1x = x end 97 | o.c1x = 43 98 | assert(setter_called) 99 | assert(o.c1x == 43) 100 | assert(getter_called) 101 | assert(o.__c1x == 43) 102 | 103 | --registering 104 | local MyClass = oo.MyClass() 105 | assert(MyClass == oo.MyClass) 106 | assert(MyClass.classname == 'MyClass') 107 | local MySubClass = oo.MySubClass'MyClass' 108 | assert(MySubClass == oo.MySubClass) 109 | assert(MySubClass.classname == 'MySubClass') 110 | assert(MySubClass.super == MyClass) 111 | 112 | --inspect 113 | print'-------------- (before collapsing) -----------------' 114 | o:inspect() 115 | 116 | --detach 117 | o:detach() 118 | assert(rawget(o, 'a') == 1) 119 | assert(rawget(o, 'b') == 'o') 120 | assert(rawget(o, 'c') == 2) 121 | 122 | --inherit, not overriding 123 | local c3 = oo.class() 124 | c3.c = 3 125 | o:inherit(c3) 126 | assert(o.c == 2) 127 | 128 | --inherit, overriding 129 | o:inherit(c3, true) 130 | assert(o.c == 3) 131 | 132 | print'--------------- (after collapsing) -----------------' 133 | o:inspect() 134 | 135 | do 136 | print'-------------- (all reserved fields) ---------------' 137 | local c = oo.TestClass() 138 | local o = c() 139 | function c:set_x() end 140 | o:inspect(true) 141 | end 142 | 143 | --performance 144 | print'------------------ (performance) -------------------' 145 | 146 | local chapter 147 | local results_t = {} 148 | 149 | local function perf_tests(title, inherit_depth, iter_count, detach) 150 | 151 | local function perf_test(sub_title, test_func) 152 | local root = oo.class() 153 | local super = root 154 | for i=1,inherit_depth do --inheritance depth 155 | super = oo.class(super) 156 | end 157 | local o = super() 158 | 159 | local rw 160 | function root:get_rw() return rw end 161 | function root:set_rw(v) rw = v end 162 | local ro = 'ro' 163 | function root:get_ro(v) return ro end 164 | function root:set_wo(v) end 165 | function root:method(i) end 166 | o.rw = 'rw' 167 | assert(rw == 'rw') 168 | o.own = 'own' 169 | o.wo = 'wo' 170 | 171 | if detach == 'copy_gp' then 172 | o.__getters = o.__getters 173 | o.__setters = o.__setters 174 | elseif detach then 175 | o:detach() 176 | end 177 | 178 | local t0 = clock() 179 | test_func(o, iter_count) 180 | local t1 = clock() 181 | 182 | local speed = iter_count / (t1 - t0) / 10^6 183 | 184 | local title = chapter..title 185 | results_t[sub_title] = results_t[sub_title] or {} 186 | results_t[sub_title][title] = speed 187 | local title = title..sub_title 188 | print(string.format('%-20s: %10.1f mil iter', title, speed)) 189 | end 190 | 191 | perf_test('method', function(o, n) 192 | for i=1,n do 193 | o:method(i) 194 | end 195 | end) 196 | perf_test('rw/r', function(o, n) 197 | for i=1,n do 198 | assert(o.rw == 'rw') 199 | end 200 | end) 201 | perf_test('rw/w', function(o, n) 202 | for i=1,n do 203 | o.rw = i 204 | end 205 | end) 206 | perf_test('rw/r+w', function(o, n) 207 | for i=1,n do 208 | o.rw = i 209 | assert(o.rw == i) 210 | end 211 | end) 212 | perf_test('ro/r', function(o, n) 213 | for i=1,n do 214 | assert(o.ro == 'ro') 215 | end 216 | end) 217 | perf_test('wo/w', function(o, n) 218 | for i=1,n do 219 | o.wo = i 220 | end 221 | end) 222 | 223 | do return end --instance fields are fast in all cases 224 | 225 | perf_test('own/r', function(o, n) 226 | for i=1,n do 227 | assert(o.own == 'own') 228 | end 229 | end) 230 | perf_test('own/w', function(o, n) 231 | for i=1,n do 232 | o.own = i 233 | end 234 | end) 235 | perf_test('own/r+w', function(o, n) 236 | for i=1,n do 237 | o.own = i 238 | assert(o.own == i) 239 | end 240 | end) 241 | end 242 | 243 | local function run_tests(mag) 244 | perf_tests('0_d', 0, 10^6 * mag, true) 245 | perf_tests('0+1_p', 0, 10^6 * mag, 'copy_gp') 246 | perf_tests('0+1', 0, 10^6 * mag, false) 247 | perf_tests('2+1', 2, 10^5 * mag, false) 248 | perf_tests('6+1', 6, 10^5 * mag, false) 249 | perf_tests('6+1_p', 6, 10^5 * mag, 'copy_gp') 250 | end 251 | 252 | chapter = 'J_' 253 | run_tests(4) 254 | 255 | chapter = 'I_' 256 | jit.off(true, true) 257 | run_tests(0.5) 258 | 259 | local function sortedpairs(t, cmp) 260 | local kt={} 261 | for k in pairs(t) do 262 | kt[#kt+1]=k 263 | end 264 | table.sort(kt) 265 | local i = 0 266 | return function() 267 | i = i + 1 268 | return kt[i], t[kt[i]] 269 | end 270 | end 271 | 272 | print() 273 | for chapter, speeds in sortedpairs(results_t) do 274 | local t = {} 275 | for title, speed in sortedpairs(speeds) do 276 | t[#t+1] = string.format('%8s', title) 277 | end 278 | print(string.format('%-7s: %s', 'TEST', table.concat(t))) 279 | break 280 | end 281 | for chapter, speeds in sortedpairs(results_t) do 282 | local t = {} 283 | for title, speed in sortedpairs(speeds) do 284 | t[#t+1] = string.format('%8.1f', speed) 285 | end 286 | print(string.format('%-7s: %s', chapter, table.concat(t))) 287 | end 288 | 289 | print() 290 | print'LEGEND:' 291 | print'I : interpreter mode' 292 | print'J : JIT mode' 293 | print'0_d : called detach() on instance' 294 | print'N+1 : N+1-level deep dynamic inheritance' 295 | print'_p : copied __getters and __setters to instance' 296 | --------------------------------------------------------------------------------