Hey,
Out of curiosity, I wanted to implement Aspect Oriented Programming into metatables used as objects.
I'm having trouble making the __index meta-method to work correctly.
My goal is whenever the functions update or draw are executed, I also execute the functions called pre_update, pre_draw and post_update and post_draw if they exist in the same object.
I can't make the code below to work. What am I doing wrong?
function aop_mt(tbl)
tbl = tbl or {}
tbl.__index = function(tbl,key)
local ogfunc = rawget(tbl, key)
if (one_of(key, "update", "draw") and is_fun(ogfunc)) then
local prefunc = rawget(tbl,"pre_"..key)
local postfunc = rawget(tbl,"post_"..key)
return function(self,...)
if(prefunc) prefunc(self,...)
local ret = ogfunc(self,...)
if(postfunc) postfunc(self,...)
return ret
end
end
return ogfunc
end
return tbl
end
--creating an object using aop_mt
object = {
items = {
{name="potion", qty=100, price=120, sell=100},
{name="medicine", qty=100, price=100, sell=80},
{name="revive", qty=10, price=300, sell=180},
{name="attack+", qty=10, price=400, sell=250},
},
--when I call this function, I also want 'pre_update' to run before, and 'post_update' to run after.
update=function(self) -- update logic end,
pre_update=function(self) -- pre-update logic end,
post_update=function(self) -- post-update logic end,
}
setmetatable(shop,aop_mt())
--utils used above for reference
function is_fun(f) return (f!=nil and type(f)=="function") end
function one_of(value, ...)
if(select("#",...)==0) return false
for arg in all({...})do if(value==arg) return true end
return false
end
|
Okay so there are a couple obvious problems:
You're calling setmetatable on shop but there's no shop object in your code. I assume object should be called shop or vice versa?
Also your update, pre_update and post_update functions all have their terminating end commented out.
The problem you're having with __index is that its purpose is to find a value to return when there isn't one defined on the object itself. In other words, shop:update() will only trigger a call to __index if shop.update == nil. Since shop.update isn't nil, there's no call to __index.
What you can do instead is use __newindex to replace the user defined update function with the one you want when it's defined.
Here's a sketch of a possible solution:
function aop()
return setmetatable(
{},
{
__newindex=function(self, key, value)
-- do a similar thing for draw
if key == 'update' then
local function _real_update(obj)
if obj.pre_update then
obj:pre_update()
end
local ret_val = value(obj) -- value is the user defined update function
if obj.post_update then
obj:post_update()
end
return ret_val
end
rawset(self, key, _real_update)
return
end
-- anything other than update or draw just set it as is.
rawset(self, key, value)
end
}
)
end
shop = aop()
function shop:update()
print('update')
end
function shop:pre_update()
print('pre_update')
end
function shop:post_update()
print('post_update')
end
shop:update() |
The above prints
pre_update update post_update |
as intended.
So I'm not super familiar with aspect oriented programming—had heard of it but never really studied it—but after skimming the wikipedia article, "adding behavior to existing code (an advice) without modifying the code," sounds similar to me to the object oriented design pattern "decorator."
So a decorator-ish approach might actually be better. Obviously I don't know exactly what you're trying to do but I think this is maybe closer to what you actually wanted than my previous answer.
function aop(obj)
return setmetatable(
{
update=function(self)
if self.pre_update then
self:pre_update()
end
local ret_val = obj.update(self)
if self.post_update then
self:post_update()
end
return ret_val
end
},
{__index=obj}
)
end
shop = {}
function shop:update()
print('update')
end
function shop:pre_update()
print('pre-update')
end
function shop:post_update()
print('post-update')
end
shop = aop(shop)
shop:update() |
[Please log in to post a comment]




