Lua 中没有常见面向对象语言中所谓类的概念,取而代之使用模块来组织管理代码。关于模块的基础知识大家可以参考「OpenResty 最佳实战」,本文聊点别的。
如何实现一个模块呢?假设我们要实现一个不太安全的房奴模块(houseslave.lua):
local _M = {} local mt = { __index = _M } function _M.new(me, bank) local t = { me = me, bank = bank, } return setmetatable(t, mt) end function _M.repay(self, money) self.me.money = self.me.money - money self.bank.money = self.bank.money + money end return _M
如果我们借用类的思维来解释这段代码,那么大概意思就是:类的属性保存在表(t)中,类的方法保存在元表(mt)中,二者通过 setmetatable 关联起来。
实际使用的时候,大致如下所示:
local houseslave = require "houseslave" local hs = houseslave.new(me, bank) hs:repay(10000)
学习模块最好的方法就是多看别人是如何搞的,但也不能完全照搬,以很多人都很熟悉的 lua-resty-redis 模块为例,如果通过 luacheck 来检查的话,会发现很多问题,我们就以 new 方法的问题为例来说明一下,官方文档的描述如下:
red, err = redis:new()
通过冒号语法糖,self 参数被隐式传递了,但这不是重点,要紧的是 self 在这里有没有意义?实际上,new 相当于是类里的构造函数,在调用构造函数之前,还没有实例化出对象,此时 self 是多余的,应该去掉 new 参数中 self 的定义,当然调用方式也要改一下:
red, err = redis.new()
如果你没搞清楚,可以多看看前面房奴的例子,体会一下「点」和「冒号」的差异。
OpenResty 通过 package.path 来查找模块,初学者往往不知道应该把自己写的模块放到哪个目录,此时可以通过 resty-cli 工具来确认你的 package.path 设置:
已经装载的模块保存在 package.loaded.* 中,于是我们可以通过 package.loaded.* = nil 的方式卸载对应的模块,如此一来就实现了热装载代码,同把大象放冰箱一样分三步:
- 把需要动态加载的代码放在一个 Lua 模块文件 foo.lua 中,并标记版本号。
- 暴露一个 location 以便从外部写最新的版本号到一个 ngx_lua 共享内存字典中。
- 在 Lua 代码中,首先检查当前 foo 模块的版本号与共享内存字典中的最新版本号是否一致;如果不一致的话,则卸载当前的 foo 模块(package.loaded.foo = nil ),然后再调用 local foo = require “foo”,从而完成热装载代码。
需要说明的是,使用了 LuaJIT FFI 的模块是不能通过清空 package.loaded 中的对应字段卸载的,好在多数时候,需要频繁热装载代码的模块往往是业务相关的模块,我们可以在设计之初,有意识的把 LuaJIT FFI 相关的代码单独剥离出来。
好了,赶在十月底最后一天完成了本月的文章,虽然没什么营养,但习惯不能丢。
评论前必须登录!
注册