用 lua 模拟面向对象
27 Aug 2017一些语言本身是没有面向对象这一说的,类似 lua, js。但是平时使用时,往往会利用语言上的特性来模拟 OO。 初学lua,也看了很多网上的资料、博客,一直不是很理解,只是照猫画虎。现在理解地差不多了,记录一下。
lua 用的是其强大的 table,可以简单地理解为 hash + array。作为 OO,我们需要模仿的有类和对象,每个对象有属性和方法。
首先我们用 table 模拟一个类,这个类有一个 new 的方法,可以生成一个对象,这个对象我们依然使用 table 进行模拟。这里的 new 并不像 c++ 中有特殊的含义,这是一个函数名,可以叫做任何名字,例如 create
Student = {}
function Student.new(name, age)
local obj = {} -- 创建一个对象 (table)
obj.name = name or '' -- 添加属性并赋值
obj.age = age or 0
return obj
end
stu = Student.new('fcnaud', 20)
print(stu.name) -- 使用属性
除过属性,一个对象还应该拥有方法。
Student = {}
function Student.new(name, age)
local obj = {} -- 创建一个对象 (table)
obj.name = name or '' -- 添加属性并赋值
obj.age = age or 0
obj.sayHi = function()
print('hi')
end
obj.showInfo = function()
print(obj.name)
-- 这里能够访问到正确的 obj,是由于闭包。你可以认为这个函数捕获到了上边那个局部变量。在 lua 里这种局部变量叫做 upvalue。
end
return obj
end
stu = Student.new('fcnaud', 20)
stu.sayHi()
stu1 = Student.new('dcf', 20)
stu1.sayHi()
功能已经基本实现了,但是又出现了另一个问题,那就是所有的函数对每个对象来说都有一份,这实际上是很浪费的。学过 c++ 大家都知道,c++ 中的类函数实际上只有在类中有一份,所有的对象调用的都是这一个函数,然后通过一个隐式的 this 指针来进行数据的调用。
在 lua 里,我们可以利用 metatable(元表)来进行模拟。不清楚 metatable 的可以先去看一下教程。这里,我们让‘类’作为所有‘对象’的 metatable,将函数定义在‘类’中,这样所有对象在调用函数时,都会前往‘类’中调用。
Student = {}
function Student.new(class, name, age)
obj = {}
setmetatable(obj, class) -- 设置元表
class.__index = class -- 这里让 __index 指向自己,一是方便操作,二是节省空间
obj.name = name or ''
obj.age = age or 0
return obj
end
function Student.sayHi()
print('hi')
end
function Student.show(this) -- 调用函数是也要知道是那个对象调用的
print(this.name)
end
stu = Student.new(Student, 'fcnaud', 20) -- 生成对象时要制定class
stu.sayHi()
stu.show(stu) -- 调用函数时要指定对象
这里我们就基本地模拟了面向对象。不过每次都要显式指定十分麻烦,lua 中有一个语法糖可以提供我们使用。
obj.func(obj) <=> obj:func()
使用冒号时,会隐式地将调用者传递进去(体现为 self,类似于 c++ 中的 this),我们修正一下
Student = {}
function Student:new(name, age) -- 使用冒号
obj = {}
setmetatable(obj, self) -- 这里的 self 指的是 Student‘类’
self.__index = self
obj.name = name or ''
obj.age = age or 0
return obj
end
function Student:sayHi() -- 虽然有的函数并不需要数据
print('hi')
end
function Student:show() -- 这个函数通常是 obj ‘对象’调用的,所以这个 self 指的是对象
print(self.name)
end
stu = Student:new('fcnaud', 20) -- 函数调用时也要使用冒号
stu:sayHi()
stu:show()
这里我以前最疑惑的就是上边那个 self 了,这个 self 在两个函数里边实际上是不同的。
最后,这里是我的一个记录,如果可能的话,希望能帮到一些人。如果有人发现文章中的错误,还望指出。
参考: