Object Properties


Some object-orientated languages (like [C#]) support properties, which appear like public data fields on objects but are really syntactic sugar for accessor functions (getters and setters). In Lua it might look like this:
obj.field = 123 -- equivalent to obj:set_field(123)
x = obj.field -- equivalent to x = obj:get_field()
Here is one way to implement such a thing in Lua:

-- 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.

See Also


RecentChanges · preferences
edit · history
Last edited March 14, 2009 12:48 pm GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /