HiveBrain v1.2.0
Get Started
← Back to all entries
patternModerate

Lua OOP and classically-styled prototypal inheritance

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
classicallyinheritanceprototypalandstyledooplua

Problem

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?

Solution

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.

Code Snippets

B.request_handlers = {
  GetHandler:new(),
  URLEncodedPostHandler:new(), 
  MultipartPostHandler:new(), 
}
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,
B.RequestHandler = B.Object:extend()
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,
-- override these
function B.RequestHandler:accept() error "not implemented" end
function B.RequestHandler:process() error "not implemented" end

Context

StackExchange Code Review Q#24979, answer score: 13

Revisions (0)

No revisions yet.