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
需要在Class
table(Class.__index
)中循环引用;只要 Class
table 可用,就可以从任何地方访问元 table,因为它 是 元 table。这样做的缺点包括(1)可能更差的性能(由于哈希冲突,因为你的元table包含方法和元方法)和(2)有点脏:如果我检查了你的 Class
table,它不会在元方法和 class 方法中整齐地分开;我必须根据方法命名自己将它分开。
这也意味着索引实例或 Class
table 允许“看到”底层实现,这些实现可能应该被抽象掉/只能通过索引访问/使用运算符/调用:Class.__index
和 instance.__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
我正在编写一个模块来生成某种加密的访问令牌。我要使用的加密库(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
需要在Class
table(Class.__index
)中循环引用;只要 Class
table 可用,就可以从任何地方访问元 table,因为它 是 元 table。这样做的缺点包括(1)可能更差的性能(由于哈希冲突,因为你的元table包含方法和元方法)和(2)有点脏:如果我检查了你的 Class
table,它不会在元方法和 class 方法中整齐地分开;我必须根据方法命名自己将它分开。
这也意味着索引实例或 Class
table 允许“看到”底层实现,这些实现可能应该被抽象掉/只能通过索引访问/使用运算符/调用:Class.__index
和 instance.__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