Log In  


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]