Lua 之 Class 强化 - 持续优化迭代中
- 原创文章,转载请注明出处:Lua 之 Class 强化
- lua 库下载
为什么要自己写 Class
Lua 作为一门脚本语言,提供了基本的逻辑处理能力(编写简单的算法处理数据),但在面对复杂的应用时(整个游戏都用 Lua 编写)还是需要能对代码有较友好的管理和编写方式来支持,以增强代码稳定性和减少后期维护成本。
面向对象是大多数语言(C++, C#, Java, TypeScript 等)的标配,以后相应提供的各种语言糖让我们编写代码时得心应手。那 Lua 能不能也拥有其它语言相应的特性呢?
在 github 上找过,也有过很多 lua class 可以使用,但基本就只实现了类的继承一些简单的面向对象的功能,而且源码可读性真不咋样,想扩展一下也挺费力的,在学习多个开源 class 后觉得靠自己能力也能写出来,所在就有了这篇文章。
Lua Class 能干什么
- 类(继承,方法重写)
- 属性 Getter/Setter
- 拦截 getter/setter
- 异步 async/await + Promise
- 方法锁定
- 属性变化监听
- 模块通信
- 信号
特性示例
-
类(继承,方法重写)
- 实现最基础的类定义,子类继承与方法重写,默认所有自定义类的基类都为
Object
- 支持运算符重载
ctor fun(...:any):void
@构造函数,所有子类(Object
的直接基类除外)都需要调用基类 ctorcall fun(...:any):any
@对象执行函数add fun(target:Object):Object
@加号重载sub fun(target:Object):Object
@减号重载equalTo fun(target:Object):boolean
@等于重载lessThan fun(target:Object):boolean
@小于重载lessEqual fun(target:Object):boolean
@小于等于重载toString fun():string
@转换成字符串
- 常用工具方法
xx.findType(className:string):Type
获取指定类名的类型xx.isType(target:any):boolean
判断指定对象是否是类型xx.isSubType(type:Type, parentType:Type):boolean
判断类型是否是指定类型子类xx.instanceOf(target:any, type:Type):boolean
判断指定对象是否是指定类型实例
- 实现最基础的类定义,子类继承与方法重写,默认所有自定义类的基类都为
------------------------------文件 Animal.lua------------------------------
---动物基类
---@class Animal:Object @by wx771720@outlook.com 2021-02-23 18:14:22
---@field name string @名字
local Animal = xx.Class("Animal")
---构造函数
function Animal:ctor(name)
self.name = name
end
function Animal:sayHello()
error("must override by subclass")
end
return Animal
------------------------------文件 Cat.lua------------------------------
---@type Animal
local Animal = require "Animal"
---猫
---@class Cat:Animal @by wx771720@outlook.com 2021-02-23 18:16:37
local Cat = xx.Class("Cat", Animal)
---构造函数
function Cat:ctor(name)
Animal.ctor(self, name)
end
function Cat:sayHello()
print(self.name .. "[Cat] say miao")
end
return Cat
------------------------------文件 Dog.lua------------------------------
---@type Animal
local Animal = require "Animal"
---狗
---@class Dog:Animal @by wx771720@outlook.com 2021-02-23 18:18:05
local Dog = xx.Class("Dog", Animal)
---构造函数
function Dog:ctor(name)
Animal.ctor(self, name)
end
function Dog:sayHello()
print(self.name .. "[Dog] say wang")
end
return Dog
------------------------------文件 Main.lua------------------------------
require "core"
---@type Cat
local Cat = require "Cat"
---@type Dog
local Dog = require "Dog"
local cat = Cat("hello kitty")
cat:sayHello()
local dog = Dog("snoopy")
dog:sayHello()
------------------------------执行 lua Main.lua 输出------------------------------
xx[lua] version: 0.0.1, Email : wx771720@outlook.com
hello kitty[Cat] say miao
snoopy[Dog] say wang
------------------------------文件 Animal.lua------------------------------
---动物基类
---@class Animal:Object @by wx771720@outlook.com 2021-02-23 18:14:22
---@field name string @[ReadOnly]名字 【修改】
---
---@field _name string @名字 【新增】
local Animal = xx.Class("Animal")
---构造函数
function Animal:ctor(name)
self._name = name --【修改】
end
function Animal:nameGetter() -- 【新增】
return self._name
end
function Animal:sayHello()
error("must override by subclass")
end
return Animal
------------------------------文件 Main.lua------------------------------
require "core"
---@type Cat
local Cat = require "Cat"
---@type Dog
local Dog = require "Dog"
local cat = Cat("hello kitty")
cat.name = "tom" -- 【新增】
cat:sayHello()
local dog = Dog("snoopy")
dog.name = "pluto" -- 【新增】
dog:sayHello()
------------------------------执行 lua Main.lua 输出------------------------------
xx[lua] version: 0.0.1, Email : wx771720@outlook.com
hello kitty[Cat] say miao
snoopy[Dog] say wang
------------------------------文件 Animal.lua------------------------------
---动物基类
---@class Animal:Object @by wx771720@outlook.com 2021-02-23 18:14:22
---@field name string @[ReadOnly]名字
---
---@field _name string @名字
local Animal = xx.Class("Animal")
---构造函数
function Animal:ctor(name)
self._name = name
end
function Animal:nameGetter()
return self._name
end
function Animal:nameSetter(value) -- 【新增】
print("name changed [" .. self._name .. "]=>[" .. value .. "]")
self._name = value
end
function Animal:sayHello()
error("must override by subclass")
end
return Animal
------------------------------执行 lua Main.lua 输出------------------------------
xx[lua] version: 0.0.1, Email : wx771720@outlook.com
name changed [hello kitty]=>[tom]
tom[Cat] say miao
name changed [snoopy]=>[pluto]
pluto[Dog] say wang
------------------------------文件 Animal.lua------------------------------
---动物基类
---@class Animal:Object @by wx771720@outlook.com 2021-02-23 18:14:22
---@field name string @[ReadOnly]名字
---
---@field _name string @名字
local Animal = xx.Class("Animal")
---构造函数
function Animal:ctor(name)
self._name = name
end
function Animal:getter(key) -- 【新增】
print("get key : " .. key)
return xx.rawGet(self, key) -- 原始方法获取值
end
function Animal:setter(key, value) -- 【新增】
print("set key : " .. key .. " => " .. value)
xx.rawSet(self, key, value) -- 原始方法设置值
end
function Animal:nameGetter()
return self._name
end
function Animal:nameSetter(value)
print("name changed [" .. self._name .. "]=>[" .. value .. "]")
self._name = value
end
function Animal:sayHello()
error("must override by subclass")
end
return Animal
------------------------------文件 Main.lua------------------------------
require "core"
---@type Cat
local Cat = require "Cat"
---@type Dog
local Dog = require "Dog"
local cat = Cat("hello kitty")
cat.name = "tom" -- 不生效
-- cat:sayHello() -- 【删除】
print(cat.name)
local dog = Dog("snoopy")
dog.name = "pluto" -- 不生效
-- dog:sayHello() -- 【删除】
print(dog.name)
------------------------------执行 lua Main.lua 输出------------------------------
xx[lua] version: 0.0.1, Email : wx771720@outlook.com
set key : _uid => xx_lua_5
set key : _name => hello kitty
set key : name => tom
name changed [hello kitty]=>[tom]
get key : name
tom
set key : _uid => xx_lua_6
set key : _name => snoopy
set key : name => pluto
name changed [snoopy]=>[pluto]
get key : name
pluto
-
异步 async/await + Promise【详细可参考 javascript 中的 Promise 用法】
- 在类方法定义前添加标记
Main.__plug_async = true
表示该方法以协程中执行,并返回Promise
对象 - 只有在
__plug_async
标识过的方法内才允许使用xx.await
来暂停当前方法 - 只要是返回
Promise
对象的方法都可以作为xx.await
的参数来等待该方法结束
- 在类方法定义前添加标记
------------------------------新建文件 Main.lua------------------------------
require "core"
---程序入口类
---@class Main:Object @by wx771720@outlook.com 2021-02-23 19:22:24
local Main = xx.Class("Main")
---构造函数
function Main:ctor()
end
Main.__plug_async = true
---登录
---@return Promise
function Main:login()
print("login start")
-- 等待配置更新完成
xx.await {self:updateConfig()}
print("login config updated")
-- 等待资源下载完成
xx.await {self:downloadAssets()}
print("login assets downloaded")
-- 等待登录请求返回的数据
local response = xx.await {self:loginRequest()}
print("login login response")
end
---更新配置(异步 - 假装使用定时器来模拟)
---@return Promise
function Main:updateConfig()
xx.await {self:sleep(1)}
end
Main.__plug_async = true
---下载资源(异步 - 假装使用定时器来模拟)
---@return Promise
function Main:downloadAssets()
xx.await {self:sleep(1)}
end
Main.__plug_async = true
---发送登录请求(异步 - 假装使用定时器来模拟)
---@return Promise @返回登录返回的数据
function Main:loginRequest()
xx.await {self:sleep(1)}
return {}
end
---将回调方式的异步(定时器)封装成可等待的形式
---@return Promise @在调用 resolve 时可以传入数据,通过 xx.await 返回
function Main:sleep(time)
---@type Promise
local promise = xx.Promise()
-- 延迟指定时间调用 promise:resolve(),这里面的 delayCall 只是假设的项目中一个定时器接口(Lua 不提供定时器功能)
-- delayCall(time, function() promise:resolve() end)
return promise
end
---构造实例
Main():login()
return Main
-
方法锁定
- 在类方法定义前添加标记
Main.__plug_lock = true
表示在该方法执行过程中忽略再次调用 - 如果该类方法返回
Promise
对象,则以Promise
对象的fulfilled
或者rejected
状态表示该方法结束 - 如果该类方法返回非
Promise
对象,则以方法最后一行代码执行结束表示该方法结束
- 在类方法定义前添加标记
------------------------------文件 Main.lua------------------------------
Main.__plug_async = true
Main.__plug_lock = true
---界面登录按钮点击监听 【新增】
---假如双击登录按钮(即连续调用该方法2次),因为该方法为异步(不可能在双击过程中执行结束),所以直接忽略第二次回调
---@return Promise
function Main:onClickHandler()
xx.await {self:login()}
end
-
属性变化监听
- 添加标记
Main.__plug_watcher = xxx
表示需要监听属性变化 - xxx 可直接为属性名字符串:表示该属性变化后派发默认事件
xx.e_property_changed
- xxx 指定为对象时,第一个参数必须为属性名
- 后面添加类方法表示属性改变时回调该类方法,参数:属性名,新值,旧值
- 后面添加字符串表示自定义事件名,如果不添加自定义事件名,会派发默认事件
xx.e_property_changed
- 仅在该类为
xx.EventDispatcher
子类时才会派发事件,但类方法回调方式支持任意类,事件回调参数为Event
对象,其中args
数据分别为:属性名,新值,旧值
- 添加标记
------------------------------文件 Data.lua------------------------------
---数据
---@class Data:EventDispatcher @by wx771720@outlook.com 2021-02-23 20:00:51
---@field avatar string @头像
---@field level number @等级
---@field coin number @金币
local Data = xx.Class("Data", xx.EventDispatcher)
---构造函数
function Data:ctor()
xx.EventDispatcher.ctor(self)
self:addEventListener("e_level_changed", self.onLevelChanged, self)
self:addEventListener(xx.e_property_changed, self.onCoinChanged, self)
end
---@param key string
---@param newValue string
---@param oldValue string
function Data:onAvatarChanged(key, newValue, oldValue)
print("callback : " .. key .. " changed " .. tostring(oldValue) .. " => " .. tostring(newValue))
end
---@param evt Event
function Data:onLevelChanged(evt)
--evt.args : key, newValue, oldValue
print("custom event : " .. evt.args[1] .. " changed " .. tostring(evt.args[3]) .. " => " .. tostring(evt.args[2]))
end
---@param evt Event
function Data:onCoinChanged(evt)
print("default event : " .. evt.args[1] .. " changed " .. tostring(evt.args[2]) .. " => " .. tostring(evt.args[3]))
end
---Callback 方式支持任意类方法
Data.__plug_watcher = {"avatar", Data.onAvatarChanged}
---自定义事件仅支持 EventDisptacher 子类
Data.__plug_watcher = {"level", "e_level_changed"}
---默认事件仅支持 EventDisptacher 子类
Data.__plug_watcher = "coin"
return Data
------------------------------文件 Main.lua------------------------------
require "core"
---@type Data
local Data = require "Data"
local data = Data()
data.avatar = "001.jpg"
data.level = 1
data.coin = 0
print()
data.avatar = "002.jpg"
data.level = 999
data.coin = 99999999
------------------------------执行 lua Main.lua 输出------------------------------
xx[lua] version: 0.0.1, Email : wx771720@outlook.com
callback : avatar changed nil => 001.jpg
default event : avatar changed 001.jpg => nil
custom event : level changed nil => 1
default event : coin changed 0 => nil
callback : avatar changed 001.jpg => 002.jpg
default event : avatar changed 002.jpg => 001.jpg
custom event : level changed 1 => 999
default event : coin changed 99999999 => 0
-
模块通信(可以参考 MVC 的通知机制,建议只在逻辑类中使用该功能,因为该类会作为全局单例使用,而且不提供接口获取该单例,正常使用为一个独立的功能模块对外的接口,保证模块间解耦)
- 在类方法前添加标记
Main.__plug_on_notice
表示该方法为对外接口 - 如果直接指定为字符串:表示该方法接口对外的通知名
- 如果指定为对象(参数无序)
- 字符串:表示该方法接口对外的通知名
- 数值:表示有多个模块的接口监听同一个通知名时的调用优先级,数值越大越优先调用(常用于广播通知)
- 布尔:表示该方法接口是否在触发(执行)一次后自动移除(即只触发一次)
- 类方法第一个参数必须为
result:NoticeResult
对象,后续参数为xx.notify(...)
参数- 所有返回数据只能通过
result.data = xxx
赋值返回 - 在处理广播通知时,高优先级的通知监听可以通过
result:stop()
阻止后续低优先级的通知监听
- 所有返回数据只能通过
- 在类方法前添加标记
------------------------------文件 LogModule.lua------------------------------
---日志模块(只能使用通知名通过 xx.notify() 调用该类的接口方法)
---@class LogModule:Object @by wx771720@outlook.com 2021-02-23 20:28:46
local LogModule = xx.Class("LogModule")
---构造函数
function LogModule:ctor()
end
LogModule.__plug_on_notice = "ni_debug"
---@param result NoticeResult
function LogModule:onDebug(result, ...)
print("log debug : ", ...)
end
LogModule.__plug_on_notice = {"ni_init", 0, true}
---@param result NoticeResult
function LogModule:on(result, ...)
print("log init : ", ...)
end
return LogModule
------------------------------文件 Main.lua------------------------------
require "core"
---@type LogModule
local LogModule = require "LogModule"
xx.notify("ni_debug", 1, "02", nil, false)
xx.notify("ni_debug", 11, "022", nil, true)
xx.notify("ni_init", 1, "02", nil, false)
xx.notify("ni_init", 11, "022", nil, true)
------------------------------执行 lua Main.lua 输出------------------------------
log debug : 1 02 nil false
log debug : 11 022 nil true
log init : 1 02 nil false
-
信号(类似于事件派发器,但不需要定义事件类型,类似于 C# 的
Delegate
)- 添加标记
Main.__plug_signal
表示定义一个信号,后直接为信号属性名 - 信号对象可以直接调用(实现了元数据中的
call
方法),并且可以传入数据,在监听回调中可通过参数evt:Event
的evt.args
中访问
- 添加标记
------------------------------文件 View.lua------------------------------
---视图逻辑类
---@class View:Object @by wx771720@outlook.com 2021-02-23 20:50:41
---@field onLogin Signal @登录按钮点击信号
local View = xx.Class("View")
---构造函数
function View:ctor()
-- 添加界面按钮点击回调(非真实代码)
-- self.ui.loginBtn:onClick = xx.Handler(self.onLoginClicked, self)
end
View.__plug_signal = "onLogin"
function View:onLoginClicked()
self.onLogin()
end
return View
------------------------------文件 Main.lua------------------------------
require "core"
---@type View
local View = require "View"
local view = View()
view.onLogin:addListener(function(evt)
print("on login trigger", evt.target == view)
end)
-- 假装点击了界面上的登录按钮
view:onLoginClicked()
------------------------------执行 lua Main.lua 输出------------------------------
xx[lua] version: 0.0.1, Email : wx771720@outlook.com
on login trigger true