Lua 简单面向对象模型
Lua 使用 table 来模拟各种数据结构,当然,也可以用它来模拟类(Class)。要实现类的模拟,我们需要找到一种方式来定义类,并从类创建实例,实现继承等。
¶0x00 Meta Table & Mate Methods
在 Lua 中,可以通过定制 table 的元表以及元方法来改变 table 的行为,例如实现运算符重载等。其中有一个很有用的元方法: __index
当访问一个 table 的属性时它是这样工作的:
- table 中是否有这个属性?Y) 返回该属性的值;
- 元表是否为空?Y)返回 nil;
- __index 是否为空? Y)返回 nil
- __index 是一个 function?Y)调用 __index(table, key) 并返回结果
- __index 是一个 table?Y)递归从 __index 指向的表中查询该属性
foo = {}
foo.bar -- nil
bar = {}
setmetatable(bar, {
__index = {
baz = 1
}
})
bar.baz -- 1
递归查表这个行为是不是很像 javascript 的原型链!如果把 Lua 的 table 比作是 Javascript 的 Object,那么元表中的 __index
就相当于 Object 的 __proto__
。与 Javascript 不同的是, Lua 中的 function 与 table 是两个不同的类型;而 Javascript 中的 Function 同时也是(is-a) Object 。
¶0x01 Class
作为一个类,它应该有一个构造函数(constructor),并且能实例化对象(new)。这些对象能使用类提供的方法:
Foo = {}
Foo.__index = Foo
function Foo:ctor(str)
self.str = str
end
function Foo:say()
print(self.str)
end
function Foo.new(...)
local instance = {}
setmetatable(instance, Foo)
instance:ctor(...)
return instance
end
foo = Foo.new("bar")
foo:say() -- output: bar
Foo.new 方法为实例创建了个空表,并将 Foo 作为它的元表。借由 Foo.__index = Foo
这个实例能够访问到 Foo 中定义的方法。
根据这个方法,我们可以创建一个类工厂:
function class()
local cls = {}
cls.__index = cls
-- a dumb ctor
function cls:ctor() end
function cls.new(...)
local instance = setmetatable({}, cls)
instance:ctor(...)
return instance
end
return cls
end
然后就可以用这个类工厂批量创建类了:
Foo = class()
-- override ctor
function Foo:ctor(str)
self.str = str
end
function Foo:say()
print(self.str)
end
foo = Foo.new("bar")
foo:say() -- output: bar
¶0x02 Inheritance
接下来我们可以给类增加继承的功能。所谓继承,即子类能够扩展超类,其实例能够使用该类以及超类的方法。
本质上就是使用元表的递归查找,不断向上检索属性和方法:
function class(super)
local cls
if super == nil then
-- table with a dumb ctor
cls = { ctor = function() end }
else
cls = setmetatable({}, super)
cls.super = super
end
cls.__index = cls
function cls.new(...)
local instance = setmetatable({}, cls)
instance:ctor(...)
return instance
end
return cls
end
试试效果:
Foo = class()
-- override ctor
function Foo:ctor(str)
self.str = str
end
function Foo:say()
print(self.str)
end
Bar = class(Foo)
function Bar:sayMore(str)
self.super.say(self) -- invoke method from super class
print(str)
end
bar = Bar.new("bar")
bar:sayMore("bar") -- output: bar bar
¶0x03 is-a
为了让继承更有说服力,我们可以参考 javascript 的 instanceof 运算符 原理实现 is-a 方法,用来检测实例与类的关系——若一个类是一个 table 的元表,则这个 table 是这个类的实例,并向上递归:
function isa(instance, Class)
local metatable = getmetatable(instance)
while metatable ~= nil do
if metatable == Class then
return true
else
metatable = getmetatable(metatable)
end
end
return false
end
测试:
Foo = class()
foo = Foo.new()
print(isa(foo, Foo)) -- true
Bar = class(Foo)
bar = Bar.new()
print(isa(bar, Bar)) -- true
print(isa(bar, Foo)) -- true
Baz = class(Foo)
baz = Baz.new()
print(isa(baz, Bar)) -- false
print(isa(baz, Foo)) -- true
¶0x04 More
说到面向对象,除了继承,还得提封装和多态。在 Lua 中,使用闭包(Closure)很容易就可以实现封装。而上面提到的 is-a 方法,对多态提供了很好的支持。作为一个轻量级脚本语言,虽然不能面面具到,但已经有了实现 OOP 的基础。