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()