从函数返回的 table 继承

Inherit from table returned from function

有一个 API 提供的函数,我们称它为 createBase 其中 returns 一个 table (对象)。我想给这个 table 添加方法,但我不能只做 x = createBase() 然后 function x:foo() 因为我有另一个类似于 createBase 的函数,但它是 createExtended.用我目前的代码可能更容易解释:

import api --I don't know how you'd do this in vanilla Lua, I'd use os.loadAPI("api") but that's computercraft specific, I think
Extended = {}
function Extended:foo()
    print("foo from extended")
end

function createExtended(params)
    x = api.createBase(params)
    Extended.__index = x
    return Extended --this is obviously wrong: you can't return a class and expect it to be an object
end

当然,这行不通:但我也不知道如何让它工作。假设 createBase 返回的 table 有一个名为 bar 的函数,它只打印 bar from base。使用此测试代码,给出以下输出:

e = createExtended()
e.foo() --prints "foo from extended"
e.bar() --nil, therefor error

除了在 createExtended 中定义 function x.bar() 之外,我怎样才能做到这一点?

提前致谢。

最简单的方法是直接将方法附加到它,而不是使用元table。

local function extend(super_instance)
    super_instance.newMethod = newMethod
    return super_instance
end

local function createExtended(...)
    return extend(createSuper(...))
end

这会起作用,除非你的 superclass 使用 __newindex(例如,防止你写入未知的 properties/methods),或者使用 [=14= 迭代键] 或 next,因为它现在将有一个额外的密钥。

如果由于某种原因您无法修改该对象,您将不得不 'wrap' 修改它。


您可以创建一个新实例,其中 "proxies" 它的所有方法、属性和运算符到另一个实例,除了它添加额外的字段和方法。

local function extend(super_instance)
    local extended_instance = {newMethod = newMethod}
    -- and also `__add`, `__mul`, etc as needed
    return setmetatable(extended_instance, {__index = super_instance, __newindex = super_instance})
end

local function createExtended(...)
    return extend(createSuper(...))
end

这将适用于简单的 classes,但不适用于所有用途:

Table 像 pairsnext 这样的迭代不会找到原始 table 中的键,因为它们实际上并不存在。如果 superclass 检查给定对象的 metatable (或者如果 superclass 实际上是一个用户数据),它也不会工作,因为你会发现改为扩展元table。

然而,许多纯 Lua classes 不会做这些事情,所以这仍然是一个相当简单的方法,可能对你有用。


你也可以做类似围棋的事情;无需使用 'extend' a class 的方法,您只需将 class 作为一个字段嵌入,并为直接调用仅调用方法的包装 class 上的方法提供便利在 'extended' class.

由于 'methods' 在 Lua 中的工作方式,这有点复杂。您无法判断 属性 是一个函数即 属性 还是它实际上是一个方法。下面的代码假定所有带有 type(v) == "function" 的属性实际上都是方法,这通常是正确的,但实际上可能不适用于您的特定情况。

在最坏的情况下,您可以手动维护您想要 'proxy' 的 methods/properties 列表,但这取决于您需要代理多少个 class 以及有多少个他们拥有的属性可能会变得笨拙。

local function extend(super_instance)
    return setmetatable({
        newMethod = newMethod, -- also could be provided via a more complicated __index
   }, {
        __index = function(self, k)
            -- Proxy everything but `newMethod` to `super_instance`.
            local super_field = super_instance[k]
            if type(super_field) == "function" then
                -- Assume the access is for getting a method, since it's a function.
                return function(self2, ...)
                    assert(self == self2) -- assume it's being called like a method
                    return super_field(super_instance, ...)
                end
            end
            return super_field
        end,
        -- similar __newindex and __add, etc. if necessary
    })
end

local function createExtended(...)
    return extend(createSuper(...))
end