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]