Object Properties |
|
obj.field = 123 -- equivalent to obj:set_field(123) x = obj.field -- equivalent to x = obj:get_field()
-- Make proxy object with property support. -- Notes: -- If key is found in <getters> (or <setters>), then -- corresponding function is used, else lookup turns to the -- <class> metatable (or first to <priv> if <is_expose_private> is true). -- Given a proxy object <self>, <priv> can be obtained with -- getmetatable(self).priv . -- @param class - metatable acting as the object class. -- @param priv - table containing private data for object. -- @param getters - table of getter functions -- with keys as property names. (default is nil) -- @param setters - table of setter functions, -- with keys as property names. (default is nil) -- @param is_expose_private - Boolean whether to expose <priv> through proxy. -- (default is nil/false) -- @version 3 - 20060921 (D.Manura) local function make_proxy(class, priv, getters, setters, is_expose_private) setmetatable(priv, class) -- fallback priv lookups to class local fallback = is_expose_private and priv or class local index = getters and function(self, key) -- read from getter, else from fallback local func = getters[key] if func then return func(self) else return fallback[key] end end or fallback -- default to fast property reads through table local newindex = setters and function(self, key, value) -- write to setter, else to proxy local func = setters[key] if func then func(self, value) else rawset(self, key, value) end end or fallback -- default to fast property writes through table local proxy_mt = { -- create metatable for proxy object __newindex = newindex, __index = index, priv = priv } local self = setmetatable({}, proxy_mt) -- create proxy object return self end
Here's some tests of that
-- Test Suite -- test: typical usage local Apple = {} Apple.__index = Apple function Apple:drop() return self.color .. " apple dropped" end local Apple_attribute_setters = { color = function(self, color) local priv = getmetatable(self).priv assert(color == "red" or color == "green") priv.color = string.upper(color) end } function Apple:new() local priv = {color = "RED"} -- private attributes in instance local self = make_proxy(Apple, priv, nil, Apple_attribute_setters, true) return self end local a = Apple:new() assert("RED" == a.color) a:drop() -- "RED apple dropped" a.color = "green" assert("GREEN apple dropped" == a:drop()) a.color = "red" assert("RED apple dropped" == a:drop()) a.weight = 123 -- new field assert(123 == a.weight) -- fails as expected (invalid color) local is_ok = pcall(function() a.color = "blue" end) assert(not is_ok) -- test: simple local T = {} T.__index = T local T_setters = { a = function(self, value) local priv = getmetatable(self).priv priv.a = value * 2 end } local T_getters = { b = function(self, value) local priv = getmetatable(self).priv return priv.a + 1 end } function T:hello() return 123 end function T:new() return make_proxy(T, {a=5}, T_getters, T_setters) end local t = T:new() assert(123 == t:hello()) assert(nil == t.hello2) assert(nil == t.a) assert(6 == t.b) t.a = 10 assert(nil == t.a) assert(21 == t.b) -- test: is_expose_private = true local t = make_proxy(T, {a=5}, T_getters, T_setters, true) assert(5 == t.a) assert(6 == t.b) print("done")
Variations of this are possible, and this might not be optimal (--RiciLake). You may have different design constraints. One suggestion was possibly to memoize the lookup Apple_attribute_funcs[key] or abstract away the actual rawset out of the setter functions.
-- DavidManura
Here is another way of doing this, shown first in lua, then again in C:
-- Rewrite in lua of array example from http://www.lua.org/pil/28.4.html -- that implements both array and OO access. array = { new = function(self, size) local o = { _size = size, _array = {}, } for i = 1, o._size do o._array[i] = 0 end setmetatable(o, self) return o end, size = function(self) return self._size end, get = function(self, i) -- should do bounds checking on array return self._array[tonumber(i)] end, set = function(self, i, v) -- should do bounds checking on array self._array[tonumber(i)] = tonumber(v) end, __index = function(self, key) return getmetatable(self)[key] or self:get(key) end, __newindex = function(self, i, v) self:set(i, v) end, }
In C, this is:
/* Rewrite in C of array example from http://www.lua.org/pil/28.4.html that implements both array and OO access. Lacks bounds checking, its not pertinent to this example. */ #include "lauxlib.h" #include "lua.h" #include <assert.h> #include <stdint.h> #include <string.h> #define ARRAY_REGID "22d3fa81-aef3-4335-be43-6ff037daf78e" #define ARRAY_CLASS "array" struct array { lua_Integer size; lua_Number data[1]; }; typedef struct array* array; static array array_check(lua_State* L, int index) { void* userdata = luaL_checkudata(L,index,ARRAY_REGID); assert(userdata); return userdata; } int array_new(lua_State* L) { // Ignoring [1], the "array" global table. int size = luaL_checkinteger(L, 2); array self = (array) lua_newuserdata(L,sizeof(*self) + (size-1) * sizeof(self->data)); self->size = size; for(size = 0; size < self->size; size++) self->data[size] = 0; luaL_getmetatable(L, ARRAY_REGID); lua_setmetatable(L, -2); return 1; } int array_size(lua_State* L) { array self = array_check(L, 1); lua_pushinteger(L, self->size); return 1; } int array_get(lua_State* L) { array self = array_check(L, 1); lua_Integer i = luaL_checkinteger(L, 2); // TODO bounds checking on i lua_pushnumber(L, self->data[i-1]); return 1; } int array_set(lua_State* L) { array self = array_check(L, 1); lua_Integer i = luaL_checkinteger(L, 2); lua_Number v = luaL_checknumber(L, 3); // TODO bounds checking on i self->data[i-1] = v; return 0; } int array_index(lua_State* L) { const char* key = luaL_checkstring(L, 2); lua_getmetatable(L, 1); lua_getfield(L, -1, key); // Either key is name of a method in the metatable if(!lua_isnil(L, -1)) return 1; // ... or its a field access, so recall as self.get(self, value). lua_settop(L, 2); return array_get(L); } static const struct luaL_reg array_class_methods[] = { { "new", array_new }, { NULL, NULL } }; static const struct luaL_reg array_instance_methods[] = { { "get", array_get }, { "set", array_set }, { "size", array_size }, { "__index", array_index }, { "__newindex", array_set }, { NULL, NULL } }; int array_open(lua_State* L) { luaL_newmetatable(L, ARRAY_REGID); luaL_openlib(L, NULL, array_instance_methods, 0); luaL_openlib(L, ARRAY_CLASS, array_class_methods, 0); return 1; }
For both implementations, array can be used as:
o = array:new(3) print(o:size()) o[1] = 1 o[2] = 2 o[3] = 3 print(o:get(2)) o:set(3, -1) print(o[3])
-- see also GeneralizedPairsAndIpairs to allow "pairs" and "ipairs" to work with this.