class 实例初始化的不同方法以及如何查找给定对象是否是特定 class 的实例

Different methods of class instance initialization and how to find if the given object is an instance of particular class

我正在编写一个模块来生成某种加密的访问令牌。我要使用的加密库(lua-resty-string.aes)使用了下面的class和class实例初始化方法:

local _M = { _VERSION = '0.14' } 
local mt = { __index = _M } 
function _M.new(...)
    ... 
    return setmetatable({ ... }, mt) 
end

这个初始化方法和下面那个(我比较熟悉)有什么根本区别?

Class = {} 
Class.__index = Class 
function Class.new()
    return setmetatable({}, Class)
end

当我需要检查传递给我的函数的变量是否是所需 class 的实例时,对于第二种情况我可以使用以下内容:

if getmetatable(obj) == Class then ...

然而,这不适用于第一种初始化情况。我发现以下工作正常:

local aes = require("resty.aes")
if (getmetatable(obj) or {}).__index == aes then ...

但我不确定是否可以,或者有什么更方便的方法吗?还有使用class实例初始化第一种方法有什么好处?

Metatable-based classes

首先,_M只是模块“命名空间”的命名约定。您可以将其命名为“模块”或“Class”,就像在您的第二个 example.The 中一样,唯一真正的区别是 metatable 和“方法table”/“class" table:

local Class = {}
local metatable = { __index = Class } 
function Class.new(...)
    ... 
    return setmetatable({ ... }, metatable) 
end

将两者完全分开; metatable 是一个局部变量,因此对 class 保持“私有”(如果设置了 __metatable 字段,它可以进一步对 getmetatable 隐藏)。如果应该公开 metatable 以便于检查,一种方法是 Class table:

中的 metatable 字段
Class.metatable = metatable

另一种模式只是将元table 和方法table 混合在同一个table 中。 Lua 的元方法命名使用双下划线作为前缀(例如 __index)旨在允许:

local Class = {}
Class.__index = Class
function Class.new(...)
    ... 
    return setmetatable({ ... }, metatable) 
end

需要在Classtable(Class.__index)中循环引用;只要 Class table 可用,就可以从任何地方访问元 table,因为它 元 table。这样做的缺点包括(1)可能更差的性能(由于哈希冲突,因为你的元table包含方法元方法)和(2)有点脏:如果我检查了你的 Class table,它不会在元方法和 class 方法中整齐地分开;我必须根据方法命名自己将它分开。

这也意味着索引实例或 Class table 允许“看到”底层实现,这些实现可能应该被抽象掉/只能通过索引访问/使用运算符/调用:Class.__indexinstance.__index 将是 Class.

实例检查

现在检查对象是否是 Class 的实例的问题:如果使用第二种方法,比较 getmetatable 的 return 值有效:

if getmetatable(obj) == Class then
    -- object is an instance of the class
end

如果 metatable 暴露为 Class.metatable 之类的(可能会让你想起 JS 中的 Object.prototype),这非常相似:

if getmetatable(obj) == Class.metatable then
    -- object is an instance of the class
end

或者,Class 可以实现它自己的 isinstance 方法来检查“私有”(本地)元table:

function Class.isinstance(table)
    return getmetatable(table) == metatable
end

允许与 Class 进行简单比较的第三种选择是将 __metatable 字段设置为 Class:

local metatable = {__index = Class, __metatable = Class}

这样,metatable 被整齐地 local 化并与 Class 分开,但从外面看起来好像使用了“糊状”模式。

如果 metatable 未公开并且没有 isinstance 方法可用,您可能不得不求助于 hack,例如您展示的实现此方法的方法:

if getmetatable(obj).__index == Class then
    -- object is an instance of the class
end

然而,此 doees 依赖于 __index 以特定方式使用 - 隐藏的实现细节 - 如果可能,不应在实践中使用。假设 __index 更改为实际元方法以记录警告,因为某个字段已被弃用:

function metatable.__index(self, field)
    if field == "deprecated_field" then warn"deprecated_field is deprecated, use field instead!"
    return Class[field]
end

突然你的支票失效了,因为 metatable.__index == Class 的假设不再成立了。要考虑的另一种方法是从实例中获取元table:

local instance = Class(...)
local class_metatable = getmetatable(instance)
...
if getmetatable(obj) == class_metatable then
    -- object is probably an instance of the class
end