Наследование метаметодов в Lua
Мне очень нравится, как объектно-ориентированное программирование описано в "программировании на языке lua" 16.1, 16.2:
http://www.lua.org/pil/16.1.html
http://www.lua.org/pil/16.2.html
и хотел бы следовать этому подходу. но я хотел бы пойти немного дальше: я хотел бы иметь базовый "класс" под названием "класс", который должен быть основой всех подклассов, потому что я хочу реализовать там некоторые вспомогательные методы (например, "instanceof" и т. д.).), но по сути это должно быть так, как описано в книге:
function class:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
Теперь к моей проблеме:
Я хотел бы иметь класс "число", который наследует от "класса":
number = class:new()
Я хотел бы определить метаметоды для перегрузки операторов (__add, __sub и т. д.) в этом классе, так что-то вроде:
n1 = number:new()
n2 = number:new()
print(n1 + n2)
работает. это на самом деле не проблема. но теперь я хотел бы иметь третий класс "деньги", который наследуется от "числа":
money = number:new{value=10,currency='EUR'}
я представляю некоторые новые свойства здесь и тому подобное.
Теперь моя проблема в том, что я не заставляю вещи работать, что "деньги" наследуют все методы из "класса" и "числа", включая все метаметоды, определенные в "числе".
Я пробовал несколько вещей, таких как перезапись "новых" или модификация метатаблиц, но я не смог заставить вещи работать, не потеряв ни методов "класса" в "деньгах", ни мета-методов "числа" в "деньгах"
я знаю, что существует множество реализаций классов, но я действительно хотел бы придерживаться минимального подхода самого lua.
Любая помощь будет очень высоко ценится!
Спасибо большое!
3 ответа
Я думаю, что проблема, с которой вы столкнулись, связана с тем, что мета-методы оператора ищутся с использованием чего-то похожего на rawget(getmetatable(obj) or {}, "__add")
, Таким образом, операторы не наследуются вместе с другими функциями.
У меня был некоторый успех с new
Функция скопировать операторы так:
function class:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
local m=getmetatable(self)
if m then
for k,v in pairs(m) do
if not rawget(self,k) and k:match("^__") then
self[k] = m[k]
end
end
end
return o
end
На этот вопрос уже дан ответ, но позвольте мне представить свое решение этой проблемы, которое я использовал некоторое время назад при разработке своей ООП- библиотеки среднего класса.
В моем случае я начинаю составлять список всех "полезных" имен метаметодов:
local _metamethods = { -- all metamethods except __index
'__add', '__call', '__concat', '__div', '__le', '__lt', '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm'
}
Я использую этот список для добавления методов в каждый создаваемый класс. Итак, по сути, у меня есть "реализация по умолчанию" для всех метаметодов:
-- creates a subclass
function Object.subclass(theClass, name)
...
local dict = theSubClass.__classDict -- classDict contains all the [meta]methods of the
local superDict = theSuperClass.__classDict -- same for the superclass
...
for _,mmName in ipairs(_metamethods) do -- Creates the initial metamethods
dict[mmName]= function(...) -- by default, they just 'look up' for an implememtation
local method = superDict[mmName] -- and if none found, they throw an error
assert( type(method)=='function', tostring(theSubClass) .. " doesn't implement metamethod '" .. mmName .. "'" )
return method(...)
end
end
Хитрость в том, что реализация по умолчанию "вызывает" реализацию родительского класса; и если этого не существует, выдает ошибку.
Метаметоды, реализованные таким образом, лишь немного медленнее, чем обычные методы (2 дополнительных вызова метода), а объем используемой памяти очень мал (12 дополнительных функций на класс).
PS: я не включил __len
метаметод, так как Lua 5.1 все равно его не уважает.
PS2: я не включал __index
или же __newindex
так как я должен использовать их для своих занятий.
Я сделал нечто подобное, и у меня была похожая проблема. Вот мое определение базового класса:
RootObjectType = {}
RootObjectType.__index = RootObjectType
function RootObjectType.new( o )
o = o or {}
setmetatable( o, RootObjectType )
o.myOid = RootObjectType.next_oid()
return o
end
function RootObjectType.newSubclass()
local o = {}
o.__index = o
setmetatable( o, RootObjectType )
RootObjectType.copyDownMetaMethods( o, RootObjectType )
o.baseClass = RootObjectType
return o
end
function RootObjectType.copyDownMetaMethods( destination, source ) -- this is the code you want
destination.__lt = source.__lt
destination.__le = source.__le
destination.__eq = source.__eq
destination.__tostring = source.__tostring
end
RootObjectType.myNextOid = 0
function RootObjectType.next_oid()
local id = RootObjectType.myNextOid
RootObjectType.myNextOid = RootObjectType.myNextOid + 1
return id
end
function RootObjectType:instanceOf( parentObjectType )
if parentObjectType == nil then return nil end
local obj = self
--while true do
do
local mt = getmetatable( obj )
if mt == parentObjectType then
return self
elseif mt == nil then
return nil
elseif mt == obj then
return nil
else
obj = mt
end
end
return nil
end
function RootObjectType:__lt( rhs )
return self.myOid < rhs.myOid
end
function RootObjectType:__eq( rhs )
return self.myOid == rhs.myOid
end
function RootObjectType:__le( rhs )
return self.myOid <= rhs.myOid
end
function RootObjectType.assertIdentity( obj, base_type )
if obj == nil or obj.instanceOf == nil or not obj:instanceOf( base_type ) then
error( "Identity of object was not valid" )
end
return obj
end
function set_iterator( set )
local it, state, start = pairs( set )
return function(...)
local v = it(...)
return v
end, state, start
end