28
\$\begingroup\$

I want to do some object-oriented programming in Lua, and I decided on something like this:

local B = {} -- in real life there is other stuff in B

B.Object = {

  constructor = function() end,

  extend = function(this, that, meta)
    if not that then that = {} end
    if not meta then meta = {} end
    meta.__index = this
    setmetatable(that, meta)
    return that
  end,

  isa = function(this, that)
    for meta in function() return getmetatable(this) end do
      if this == that then return true end
      this = meta.__index
    end
    return this == that
  end,

  new = function(this, ...)
    local that = this:extend()
    that:constructor(...)
    return that
  end,

}

All objects (including "class-like objects") extend B.Object, directly or indirectly. For example:

-- Request handler base

B.RequestHandler = B.Object:extend()

-- override these
function B.RequestHandler:accept() error "not implemented" end
function B.RequestHandler:process() error "not implemented" end

-- URL-encoded POST handler

B.PostHandler_URLEncoded = B.RequestHandler:extend()

function B.PostHandler_URLEncoded:accept()
  return self.method == "POST" and 
         self.content_type == "application/x-www-form-urlencoded"
end

function B.PostHandler_URLEncoded:process()     
  -- some code
end

-- Multipart POST handler

B.PostHandler_Multipart = B.RequestHandler:extend()
-- etc.

It might be used something like this:

B.request_handlers = {
  GetHandler:new(),
  URLEncodedPostHandler:new(), 
  MultipartPostHandler:new(), 
}

B.handle_request = function()
  for k, v in ipairs(B.request_handlers) do
    if v:accept() then
      return v:process()
    end
  end
  error "request not handled"
end

As far as I know, this is pretty much the normal way to handle inheritance in Lua. Did I miss anything? Is there anything that needs improvement?

\$\endgroup\$
  • 2
    \$\begingroup\$ I would suggest adding some comments to describe the intent of your code. The biggest issue is probably private properties. I tried some ideas some time ago, but every option is either horribly incomplete (and as such would require loads of boilerplate code) or will introduce at least two more levels to the call stack. So my suggestion is: Try it without inheritance. Most issues of code replication can be resolved without inheritance. \$\endgroup\$ – dualed May 6 '15 at 13:51
12
+100
\$\begingroup\$

Example Usage

The example that you provide does not really help to explain how the code should work. I trust that it does work, but the variables don't exactly match up.

For example, this code here:

B.request_handlers = {
  GetHandler:new(),
  URLEncodedPostHandler:new(), 
  MultipartPostHandler:new(), 
}

What is B.request_handlers before this? You define the "class" like so in the former code block: B.RequestHandler = B.Object:extend() but now it is lower case and has an underscore. The same for the three calls inside those brackets. What does GetHandler call? I don't see a matching method anywhere. It's all just a little confusing.

Lack of Comments

The first code block is the most important one, because that is where you are establishing the actual Lua inheritance. The problem with the code it that it is very dense, and there is no explanation whatsoever about how it is expected to be used.

For example, you define the extend method like this:

extend = function(this, that, meta)
  if not that then that = {} end
  if not meta then meta = {} end
  meta.__index = this
  setmetatable(that, meta)
  return that
end,

But then in your example you call it like this:

B.RequestHandler = B.Object:extend()

After reading through the method, I can understand that if extend() is called without any arguments, the arguments will be filled in inside the method. I think that it would be very helpful to have a comment explaining the valid ways of calling the extend method. In most languages, when a method has arguments they must be supplied or the compiler will complain. Since Lua is a dynamic language, you won't get this checking, and so I believe the comments explaining how to use your OOP implementation become even more important.

This, That, and the other

This code is very hard to understand:

isa = function(this, that)
  for meta in function() return getmetatable(this) end do
    if this == that then return true end
    this = meta.__index
  end
  return this == that
end,

new = function(this, ...)
  local that = this:extend()
  that:constructor(...)
  return that
end,

All of the this, that, then, this == that, return that, etc etc just twists my brain up into a knot. If I read the code very carefully line by line, I can eventually puzzle out what it does, but I think that this is a failure of the code.

It's true, you probably do not intend anyone to read your inheritance code. Rather, once you have set it up, it will just work and allow someone using the code to extend objects in a classical OOP way. However, I still think that by using better variable names, some comments, and a few more lines of code, it could be written in such a way that it could be understood immediately when reading it.

Also consider that someone who is not a native English speaker may struggle with the difference between this and that, and the then keyword does not help the situation.

Something Good

I like this code here:

-- override these
function B.RequestHandler:accept() error "not implemented" end
function B.RequestHandler:process() error "not implemented" end

The comment makes it very clear that these are the methods that should be overridden, and the string that will be returned if they are not will make it very clear what has gone wrong.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.