Lua oop
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, 的 metatable 为 function_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, 再把这个table的metatable设置为这个名为Class的table么? 可以封装一下么?
如下, 我们一个一个解决:
每次我都要使用 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, 再把这个 table 的 metatable 设置为这个名为 Class 的 table 么? 可以封装一下么?
我们先写一个函数
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
如上, 当我们调用的时候, 返回一个 metatable 为 metaTable 的 table.
貌似不是太优雅…
如果我们直接给 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
当 metaTable 为 Class 时 :
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()


