Luaclass 的轻量版,没有那么多功能,但能显著改善 Lua 的 OOP 体验
Note:
simpleclass的类型结构比luaclass更简单,因此有更好的 LS 类型推导支持
同样的,下载主文件夹 simpleclass 到模块目录,然后在 Lua 代码中导入即可
require "simpleclass"这会将所有模块接口注册到 _G,如果你不希望这样,可以只:
local simpleclass = require "simpleclass.local"简单的情况用起来与 Luaclass 相差无几
require "simpleclass"
class "MyClass" {
foo = function(self)
print("foo from "..cls_type(self))
end;
}
local obj = MyClass()
obj:foo() --> "foo from MyClass"类是 LuaSimpleClass 的核心,类是生成对象的蓝图,使多个对象共享相同的方法,同时在构造函数中规定对象应有的属性
- 定义命名类:语法为
class "<name>" {<body>}
命名类创建后位于
class._ENV环境中,同名的类会覆盖之前的定义;同时,在全局模式下,如果此时_G中没有其他同名变量,会自动创建全局变量<name>指向该类
- 静态方法: 没有
self参数的函数,用.调用 - 对象方法: 第一个参数为
self的方法,用:调用 - 构造函数:方法签名为
__init(self, ...) --> nil - 实例化:
clazz()或clazz:new()均可,这里以前者为例
示例:命名类
class "MyNamedClass" { __init = function(self, arg1, arg2) self.arg1 = arg1 self.arg2 = arg2 end; print = function(self) print(self.arg1, self.arg2) end; } local obj = MyNamedClass("hello", "world") obj:print() --> "hello world"
- 匿名类:无名的类,不会注册到任何环境
示例:匿名类
local cls = class { foo = function(self) print("foo from anonymous class") end; } local obj = cls() obj:foo() --> "foo from anonymous class"注:定义有名类时如果传入空参数,空字符串,非字符串参数,一律会被解释为匿名类;如
class () {}也能创建匿名类,这和class {}方式的不同之处在于,它可以像命名类一样用extendsimplements
- 单继承:本模块仅支持单继承,使用
extends关键字
extends接受类名字符串,因此匿名类不能作为父类;但匿名类可以继承其他类,这一点与 Java 类似
super:接收当前类与self,如super(MySubclass, self):__init()
示例:单继承
class "MySubClass" : extends "MyClass" { ---@Override foo = function(self) super(MySubclass, self):foo() -- call parent's foo print("improved foo") end; } local obj = MySubClass() obj:foo() --> foo from MySubClass --| improved foo
接口是一组方法签名,定义了类必须实现的方法
接口不是类,不能实例化
接口可以被类实现,可以检查类是否实现了接口,这与继承相似;同时接口之间也存在类似继承的关系,但不会形成继承链
- 接口定义:使用
interface,语法类似于类定义 - 匿名接口:类似于匿名类
命名接口同样在
class._ENV中,与命名类的处理规则完全相同
示例:接口定义
interface "CanFly" { "fly"; -- only method names }
- 接口实现:使用
implements关键字,类可以实现多个接口
示例:接口实现
class "Bird" : implements(CanFly) { fly = function(self) print("bird is flying") end; } local obj = Bird() obj:fly() --> "bird is flying"
定义接口时使用 extends 关键字可以组合多个接口
示例:接口组合
interface "CanEat" { "eat"; } interface "BirdLike" : extends(CanEat, CanFly) { "spawn"; "nest"; }
如上,组合其他接口的同时,还可以定义新的方法
实际上,不同于定义类,空接口的 {} 完全可以省略
cls_type(obj)
- 返回对象的类
- 如果是基本类型,返回
type(obj) - 这是它在
_G中的名字(因为_G已有type),在模块中名为type
isinstance(obj, cls_or_type)
- 判断对象是否为指定类或其子类,或接口
- 也可以用于检查基本类型
- 与对象方法
obj:isInstance(clazz)等价
issubclass(sub_cls, super_cls)
- 判断子类是否为父类或其祖先类
- 与类方法
clazz:isExtends(base)等价 - 注:同一个类会返回
true
cls:isImplements(...interface)
- 判断类是否实现了指定接口,可以多个
- 没有对应的全局函数(我认为没有必要)
上面几个方法接受或返回的类或接口都是对象本身
simpleclass 提供了与 lua-language-server 兼容的类型推导插件,下面是一个参考的 LS 配置:
"runtime.plugin": ".luals/simpleclass.plugin.lua"复制插件脚本,并且保持 simpleclass 文件夹在 LS 支持的模块目录中即可
大致的功能:
- 为新建的类和接口生成 @class 标注
- 自动包含用户定义的类成员(包括 Getter 对应的属性)
- 自动生成 new 方法的签名
- 自动分析继承链与接口
- 提供 @override,用于检查超类上是否有同名方法
Warnning:
静态推导依赖于 LuaSimpleClass DSL,如果你使用了非全局的导入方式,则需要在每个文件中手动 local 所用到的模块接口,使得函数名与全局导入时匹配
接口模块是独立可选的,默认导入。如果要裁剪接口相关功能,只需从 local.lua 中删去这一行
require "simpleclass.interface"