dcf's blog 蛋炒饭是什么鬼

用 lua 模拟面向对象


一些语言本身是没有面向对象这一说的,类似 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 在两个函数里边实际上是不同的。

最后,这里是我的一个记录,如果可能的话,希望能帮到一些人。如果有人发现文章中的错误,还望指出。


参考:

知识共享许可协议 除非特别声明,作品均采用知识共享署名 4.0 国际许可协议进行许可。