Lua 是一门很简单的语言. 本身语言设计中没有原生的 oop 设计, 但是我们可以使用 setmetatable 来做一个伪装的 class.

setmetatable

setmetatable 类似于 python__metaclass__, 是用来设置一个 table 的元 table. 如下为 coolshell 中的一个简单的例子:

fraction_a = {numerator=2, denominator=3}
fraction_b = {numerator=4, denominator=7}

如上, 我们设置了两个分数, 分别为 fraction_a, fraction_b. 我们想要对这两个分数进行相加操作. 直接使用 + 必然是不行的.

Lua 给我们提供了 setmetatable. 如下, 我们来让 fraction_a, fraction_b 可以相加.

1. 创建一个 metatable

funciton_op = {}

2.metatable__add 方法进行完善, 要求传入两个 table, 可以对这两个 table 按照分数相加的算法进行执行。


function_op.__add = function(f1, f2)
    ret = {}
    ret.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator
    ret.denominator = f1.denominator * f2.denominator
    return ret
end

3. 我们使用 setmetatable, 来设定 fraction_a, fraction_b, 的 metatablefunction_op


setmetatable(fraction_a, function_op)
setmetatable(fraction_b, function_op)

这样, 当我们执行 fraction_a + fraction_b 时, 会自动调用自己所属的 metatable__add 方法, 同时传入自己, + 后面的操作变量作为第二个参数传入.

如上, 当我们调用 fraction_a + fraction_b 时候, 会执行 funciton_op.__add(fraction_a, fraction_b);

: metatable 可以理解为 javascript 中的 prototype.

metatable 还支持非常多的 metamethod, 如下:

metamethod 说明
__add(a, b) 对应表达式 a + b
__sub(a, b) 对应表达式 a - b
__mul(a, b) 对应表达式 a * b
__div(a, b) 对应表达式 a / b
__mod(a, b) 对应表达式 a % b
__pow(a, b) 对应表达式 a ^ b
__unm(a) 对应表达式 -a
__concat(a, b) 对应表达式 a .. b
__len(a) 对应表达式 #a
__eq(a, b) 对应表达式 a == b
__lt(a, b) 对应表达式 a < b
__le(a, b) 对应表达式 a <= b
__index(a, b) 对应表达式 a.b
__newindex(a, b, c) 对应表达式 a.b = c
__call(a, …) 对应表达式 a(…)

如何让 Lua 可以 OOP

如果我们设定了一个 table, 给这个 table 设定了一些方法, 那么别人来调用这个 table, 同时传入我自己, 是不是就可以 OOP 了 ?

local Class = {}    --创建了一个名为 Class 的 class

Class.getName = function(obj) –给 Class 设定了一个 getName 的方法
return obj.name
end

Class.setName = function(obj, name) –给 Class 设定了一个 setName 的方法, 对传入的对象, 我们可以设置
obj.name = name
end

c = {} –初始化一个 table

Class.setName(c, 'mario') –调用 Class.setName, 给 c 设定 name

print(Class.getName(c)) –调用 Class.getName, 用来获取 c 的 name

上面的代码有这么几个问题:

  • 每次我都要使用 Class 作为前缀, 使用 Class 的方法来操作 obj, 有点麻烦
  • Class 的这几个方法, 第一个参数都是 obj, 有啥办法能省略么 ?
  • 我每次都要初始化一个 table, 再把这个 tablemetatable 设置为这个名为 Classtable 么? 可以封装一下么?

如下, 我们一个一个解决:

每次我都要使用 Class 作为前缀, 使用 Class 的方法来操作 obj, 有点麻烦

local Class = {}    --创建了一个名为 Class 的 class

Class.getName = function(obj) –给 Class 设定了一个 getName 的方法
return obj.name
end

Class.setName = function(obj, name) –给 Class 设定了一个 setName 的方法, 对传入的对象, 我们可以设置
obj.name = name
end

c = {} –初始化一个 table

setmetatable(c, {__index=Class})

–[[
这个命令分为两步:
第一步, 创建了一个 table, 设定 table 的 __index 为 Class
第二步, 设定 c 的 metatable 是这个新创建的 table
这样, 当 c 如果调用不存在的函数, 会尝试去调用 Class 的函数, 同时把 c 当做第一个参数传入进去
–]]

c.setName(c, 'mario') –由于设定了 metatable, 这么调用可行

print(c.getName(c)) – 由于设定了 metatable, 这么调用可行

如上, 我们每次调用, 就不需要写 Class 了, 直接写新生成的 table 名称即可.

Class 的这几个方法, 第一个参数都是 obj, 有啥办法能省略么 ?

第二个问题呢, lua 自己提供了一个语法糖,如下:


local Class = {}    --创建了一个名为 Class 的 class

function Class:getName() –给 Class 设定了一个 getName 的方法
return self.name
end

–[[
相当于:

Class.getName(obj)
return obj.name
end
–]]

function Class:setName(name) –给 Class 设定了一个 setName 的方法, 对传入的对象, 我们可以设置
self.name = name
end

–[[
相当于:

Class.setName(obj, name)
obj.name = name
end
–]]

c = {} –初始化一个 table

setmetatable(c, {__index=Class})

–[[
这个命令分为两步:
第一步, 创建了一个 table, 设定 table 的 __index 为 Class
第二步, 设定 c 的 metatable 是这个新创建的 table
这样, 当 c 如果调用不存在的函数, 会尝试去调用 Class 的函数, 同时把 c 当做第一个参数传入进去
–]]

c:setName('mario') –由于设定了 metatable, 这么调用可行
–[[
相当于调用了 c.setName(c, 'mario')
–]]

print(c:getName()) – 由于设定了 metatable, 这么调用可行
–[[
相当于 print(c.getName©)
–]]

如上, 我们使用 function Class:getName() 替代了 Class.getName = function(obj). 同时, 给所有的代码中增加了一个隐形的 self 表示调用当前函数的 table

我每次都要初始化一个 table, 再把这个 tablemetatable 设置为这个名为 Classtable 么? 可以封装一下么?

我们先写一个函数

function ClassObject(metaTable, obj) --创建函数, 传入一个 obj
<span class="k">if</span> <span class="n">obj</span> <span class="o">==</span> <span class="kc">nil</span> <span class="k">then</span>
    <span class="n">obj</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">end</span>
<span class="k">return</span> <span class="nb">setmetatable</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="p">{</span><span class="n">__index</span><span class="o">=</span><span class="n">metaTable</span><span class="p">})</span> <span class="c1">--设定这个 obj 的 metatable 的 __index 为 metaTable</span>

end

如上, 当我们调用的时候, 返回一个 metatablemetaTabletable.

貌似不是太优雅…

如果我们直接给 Class 这个 table 设定一个 new 函数呢?

local Class = {}

Class.new = function(metaTable, newObj) –当我们传入 metaTable, newObj, 返回了 newObj, 并且 newObj 的 metatable 的 __index 为 metaTable

<span class="k">if</span> <span class="n">newObj</span> <span class="o">==</span> <span class="kc">nil</span> <span class="k">then</span>
    <span class="n">newObj</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">end</span>

<span class="k">return</span> <span class="nb">setmetatable</span><span class="p">(</span><span class="n">newObj</span><span class="p">,</span> <span class="p">{</span><span class="n">__index</span><span class="o">=</span><span class="n">metaTable</span><span class="p">})</span> <span class="c1">--setmetatable 返回第一个参数</span>

end

Class.new(Class, {}) –创建这个 table

metaTableClass 时 :

local Class = {}

Class.new = function(Class, newObj)
if newObj == nil then
newObj = {}
end
return setmetatable(newObj, {__index=Class})

这样, 貌似还是不好看. 我们看上面解决问题 2 的时候的语法糖, 继续改写上面的代码

local Class = {}

function Class:new(obj)

<span class="k">if</span> <span class="n">obj</span> <span class="o">==</span> <span class="kc">nil</span> <span class="k">then</span>
    <span class="n">obj</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">end</span>

<span class="k">return</span> <span class="nb">setmetatable</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="p">{</span><span class="n">__index</span><span class="o">=</span><span class="n">self</span><span class="p">})</span>

end

obj1 = Class:new() –不给 obj 设置默认值
obj2 = Class:new({name='mario'}) –给 obj 设置默认值

如上, 最后我们总结一下代码

local Class = {}

function Class:new(obj)
if obj == nil then
obj = {}
end

<span class="k">return</span> <span class="nb">setmetatable</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="p">{</span><span class="n">__index</span><span class="o">=</span><span class="n">self</span><span class="p">})</span>

end

function Class:getName()
return self.name
end

function Class:setName(name)
self.name = name
end

–如下为调用方法

obj = Class:new()

obj:setName('obj') –设定 name

print(obj:getName()) –返回上面设置的 obj

最后的最后, 当我们要创建一个类, 可以直接:

local className = {}

function className:new(obj)
–返回一个 metatable 为 {__index=self} 的 table
end

function className:method1()
end

function className:method2()
end

– 等等等

– 实例化

obj = className:new()

– 调用方法:

obj:method1()
obj:method2()