Lua 之 Class 强化 - 持续优化迭代中

为什么要自己写 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 的直接基类除外)都需要调用基类 ctor
      • call 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
  • 属性 Getter

------------------------------文件 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
  • 属性 Setter

------------------------------文件 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
  • 拦截 getter/setter

------------------------------文件 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:Eventevt.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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wx771720

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值