美文网首页
Lua class 的几种实现

Lua class 的几种实现

作者: 莫忘初心_倒霉熊 | 来源:发表于2021-01-12 14:33 被阅读0次

    原文地址:https://zhuanlan.zhihu.com/p/123971515
    本篇文章介绍的是 lua 的几种 class 实现。适合有 lua 开发经验和了解 oop 编程的开发人员阅读。

    一、Lua 实现 class 的基本方式

    众所周知,lua 可以通过元表来实现一些骚操作,目前主流的 class 实现也是通过元表来实现的。

    local t = {}
    local mt = {
        __index = {
            a = 10,
            b = 20,
            add = function(a, b)
                return a + b
            end
        }
    }
    setmetatable(t, mt)
    print(string.format("%d + %d = %d", t.a, t.b, t.add(t.a, t.b)))
    -- 10 + 20 = 30
    

    通过元表 mt 的 index 字段可以让表 t 获得一些本身没有的字段, 通过这样的一个形式,我们就可以达到从实例中调用类的方法,但是实例的成员变量又是相互独立的。另外,__index 也可以是方法,这里只简单介绍使用的关键部分,有兴趣的可以看下附链接。

    Lua 元表(Metatable)​www.runoob.com

    通过元表就可以简单的实现一个 class 函数,用来作为类的声明了:

    -- 声明一个 lua class
    -- className 是类名
    -- super 为父类
    local function class(className, super)
        -- 构建类
        local clazz = { __cname = className, super = super }
        if super then
            -- 设置类的元表,此类中没有的,可以查找父类是否含有
            setmetatable(clazz, { __index = super })
        end
        -- new 方法创建类对象
        clazz.new = function(...)
            -- 构造一个对象
            local instance = {}
            -- 设置对象的元表为当前类,这样,对象就可以调用当前类生命的方法了
            setmetatable(instance, { __index = clazz })
            if clazz.ctor then
                clazz.ctor(instance, ...)
            end
            return instance
        end
        return clazz
    end
    

    以上就是一个简单的 class 实现,通过 metatable 的嵌套设置, 可以保证实例一定可以访问到含有同名函数的最近一个父类的方法。

    下面为测试

    local printf = function(str, ...)
        return print(string.format(str, ...))
    end
    
    -- 声明  classA
    local ClassA = class("ClassA")
    ClassA.static = 'Static A'
    function ClassA:ctor(a, b)
        self.a = a or 0
        self.b = b or 0
    end
    
    function ClassA:print()
        printf("%s, a = %s, b = %d, static = %s", self.__cname, self.a, self.b, self.static)
    end
    
    function ClassA:getSum()
        return self.a + self.b
    end
    
    -- 声明  classB, 并且继承 ClassA
    local ClassB = class("ClassB", ClassA)
    function ClassB:ctor(...)
        self.super.ctor(self, ...)
    end
    
    -- overwrite
    function ClassB:print()
        print('Class B overwrite super print')
    end
    
    -- 声明 classC, 并且继承 ClassB
    local ClassC = class("ClassC", ClassA)
    ClassC.static = 'Static C'
    
    local obja1 = ClassA.new(10, 20)
    local obja2 = ClassA.new(100, 200)
    local objb1 = ClassB.new(1, 2)
    local objc = ClassC.new(3, 4)
    obja1:print()
    obja2:print()
    objb1:print()
    objc:print()
    printf("3 + 4 = %s", objc:getSum())
    --[[
    ClassA, a = 10, b = 20, static = Static A
    ClassA, a = 100, b = 200, static = Static A
    Class B overwrite super print
    ClassC, a = 3, b = 4, static = Static C
    3 + 4 = 7
    ]]
    

    以上就是实现了一个基本的 class 实现,接下来去对比、赏析一些其他的 class 实现。

    以下部分完全搬运,会附上链接,最后阶段会添加一段对这些实现的个人看法。

    二、cocos2dx-lua 中的 class 实现

    function class(classname, super)
        local superType = type(super)
        local cls
    
        if superType ~= "function" and superType ~= "table" then
            superType = nil
            super = nil
        end
    
        if superType == "function" or (super and super.__ctype == 1) then
            -- inherited from native C++ Object
            cls = {}
    
            if superType == "table" then
                -- copy fields from super
                for k,v in pairs(super) do cls[k] = v end
                cls.__create = super.__create
                cls.super    = super
            else
                cls.__create = super
                cls.ctor = function() end
            end
    
            cls.__cname = classname
            cls.__ctype = 1
    
            function cls.new(...)
                local instance = cls.__create(...)
                -- copy fields from class to native object
                for k,v in pairs(cls) do instance[k] = v end
                instance.class = cls
                instance:ctor(...)
                return instance
            end
    
        else
            -- inherited from Lua Object
            if super then
                cls = {}
                setmetatable(cls, {__index = super})
                cls.super = super
            else
                cls = {ctor = function() end}
            end
    
            cls.__cname = classname
            cls.__ctype = 2 -- lua
            cls.__index = cls
    
            function cls.new(...)
                local instance = setmetatable({}, cls)
                instance.class = cls
                instance:ctor(...)
                return instance
            end
        end
    
        return cls
    end
    

    chukong/quick-cocos2d-x​github.com[图片上传失败...(image-8e33fe-1610433106438)]

    三、toluaframework 中的 class 实现

    --Author : Administrator
    --Date   : 2014/11/25
    
    --声明,这里声明了类名还有属性,并且给出了属性的初始值。
    LuaClass = {x = 0, y = 0}
    
    --这句是重定义元表的索引,就是说有了这句,这个才是一个类。
    LuaClass.__index = LuaClass
    
    --构造体,构造体的名字是随便起的,习惯性改为New()
    function LuaClass:New(x, y) 
        local self = {};    --初始化self,如果没有这句,那么类所建立的对象改变,其他对象都会改变
        setmetatable(self, LuaClass);  --将self的元表设定为Class
        self.x = x;
        self.y = y;
        return self;    --返回自身
    end
    
    --测试打印方法--
    function LuaClass:test() 
        logWarn("x:>" .. self.x .. " y:>" .. self.y);
    end
    

    jarjin/LuaFramework_UGUI_V2​github.com

    图标

    三、xlua-framework 中的 class 实现

    --[[
    -- added by wsh @ 2017-11-30
    -- Lua面向对象设计
    --]]
    
    --保存类类型的虚表
    local _class = {}
    
    -- added by wsh @ 2017-12-09
    -- 自定义类型
    ClassType = {
        class = 1,
        instance = 2,
    }
    
    function BaseClass(classname, super)
        assert(type(classname) == "string" and #classname > 0)
        -- 生成一个类类型
        local class_type = {}
    
        -- 在创建对象的时候自动调用
        class_type.__init = false
        class_type.__delete = false
        class_type.__cname = classname
        class_type.__ctype = ClassType.class
    
        class_type.super = super
        class_type.New = function(...)
            -- 生成一个类对象
            local obj = {}
            obj._class_type = class_type
            obj.__ctype = ClassType.instance
    
            -- 在初始化之前注册基类方法
            setmetatable(obj, { 
                __index = _class[class_type],
            })
            -- 调用初始化方法
            do
                local create
                create = function(c, ...)
                    if c.super then
                        create(c.super, ...)
                    end
                    if c.__init then
                        c.__init(obj, ...)
                    end
                end
    
                create(class_type, ...)
            end
    
            -- 注册一个delete方法
            obj.Delete = function(self)
                local now_super = self._class_type 
                while now_super ~= nil do   
                    if now_super.__delete then
                        now_super.__delete(self)
                    end
                    now_super = now_super.super
                end
            end
    
            return obj
        end
    
        local vtbl = {}
        _class[class_type] = vtbl
    
        setmetatable(class_type, {
            __newindex = function(t,k,v)
                vtbl[k] = v
            end
            , 
            --For call parent method
            __index = vtbl,
        })
    
        if super then
            setmetatable(vtbl, {
                __index = function(t,k)
                    local ret = _class[super][k]
                    --do not do accept, make hot update work right!
                    --vtbl[k] = ret
                    return ret
                end
            })
        end
    
        return class_type
    end
    

    四、lua users 中的 class 实现

    function class(def)
        local class = {}
        local parents = {}
    
        local upv
        local env = _G
    
        local wraps
        local function super(parent_class)
            if not parent_class then
                parent_class = parents[1]
            end
    
            local this = this
            local that = {}
            for k, v in pairs(parent_class) do
                that[k] = type(v) == 'function' and wraps(this, v) or v
            end
    
            return setmetatable(that, that)
        end
    
        function wraps(this, func)
            return function(...)
                local t = env.this
                local s = env.super
    
                env.this = this
                env.super = super
    
                local ret = pcall(func, ...)
    
                env.this = t
                env.super = s
    
                return ret
            end
        end
    
        function class.__init()
        end
    
        for i = 1, math.huge do
            inherit, v = debug.getlocal(def, i)
            if not inherit then
                break
            end
    
            local parent_class = _G[inherit]
            for i = 1, math.huge do
                local name, pclass = debug.getlocal(2, i, 1)
                if not name then
                    break
                elseif name == inherit then
                    parent_class = pclass
                    break
                end
            end
    
            if parent_class and type(parent_class) == 'table' then
                table.insert(parents, parent_class)
                for k, v in pairs(parent_class) do
                    class[k] = v
                end
            else
                error(string.format('Class "%s" not valid.', name))
            end
        end
    
        for i = 1, math.huge do
            local name, value = debug.getupvalue(def, i)
            if not name then
                break
            elseif name == '_ENV' then
                env = value
                upv = i
                break
            end
        end
    
        local _env = setmetatable({}, {
            __index = function(t, name)
                local value = class[name]
                return value ~= nil and value or env[name]
            end,
            __newindex = function(t, name, value)
                class[name] = value
            end
        })
    
        local function senv(env)
            if upv then
                debug.setupvalue(def, upv, env)
            else
                _G = env
            end
        end
    
        senv(_env)
        env.pcall(def, env.table.unpack(parents))
        senv(env)
    
        return setmetatable({}, {
            __ipairs = function()
                return ipairs(class)
            end,
            __pairs = function()
                return pairs(class)
            end,
            __index = function(t, name)
                return class[name]
            end,
            __index_new = function(t, name, value)
                class[name] = value
            end,
            __call = function(...)
                local this = {}
                for k, v in pairs(class) do
                    this[k] = type(v) == 'function' and wraps(this, v) or v
                end
                this.__class = class
                this.__init(...)
    
                return setmetatable(this, this)
            end
        })
    end
    --Example:
    global = true
    Inherit = class(function()
        this_is_a_property_of_Inherit = true
    
        function __init()
            print('Inherit().__init()')
            this.init = true
        end
    
        function __call()
            print('Yay! You\'re calling for me :) init:', this.init, '\n')
        end
    end)
    
    Example = class(function(Inherit)
        property = "Class property"
        print('Inherited property:', this_is_a_property_of_Inherit)
        print('Global variable:   ', global, '\n')
        function __init()
            print('Example().__init()')
            super().__init()
            print('this.init:', this.init)
        end
    
        function test(...)
            print(..., this.__init, '\n')
        end
    end)
    
    example = Example()
    example.test('__init:')
    --example()
    --
    ----example.property = 'I\'m a property of instance "example"'
    --print('example.property', example.property)
    --print('Example.property', Example.property)
    

    Simple Lua Classes​lua-users.org

    这个实现方式很特殊,不是那么直白的使用 metatable 去实现,而是通过 debug API 实现的。熟悉 lua 的朋友可能看得出来这个类似与 setfenv,因为只有 lua 5.1 才有 setfenv 和 getfenv,这个是 lua5.3 上的实现。关于 5.3 上的实现可以参考

    https://leafo.net/guides/setfenv-in-lua52-and-above.html​leafo.net

    五、总结

    纵观以上的 class 方式实现,可以分为两种实现方法,一种是 metatable, 一种是 function env,对于刚接触 lua 的来说,可能会喜欢第二种,它也和 JavaScript 类似。不过在商业开发中,更多的还是要考虑可读性、 IDE 的配合、 breakpoint 的可行性,还是建议使用 metatable 的方式。

    对于我个人而言,比较倾向第一种(毕竟我自己写的)或者 cocos2d-x 的实现方式,比较清晰。tolua 的实现是基本每个类都要自己写,不太方便扩展和协同,xlua 目前新加入项目使用的就是这个,槽点比较多:

    1. 多加了一个析构函数,其实这个没必要,另外写一个 IDispose 继承即可
    2. 默认 __init 为构造函数,个人还是喜欢 ctor 或者和 TypeScript 一样写 constructor 一样会更好理解。
    3. 最后 return 那一块 *--do not do accept, make hot update work right! *显得多余了,做所谓的暖更新一没必要,二可能会出现意向不到的错误,重启 lua 虚拟机是一个比较稳妥的方案。

    这里推荐一款 lua IDE 插件 -- EmmyLua,可以被安装下 vscode 或者 jetbrian 系列上,可以对 lua 进行语法分析、断点调试。

    https://github.com/EmmyLua/IntelliJ-EmmyLua​github.com

    相关文章

      网友评论

          本文标题:Lua class 的几种实现

          本文链接:https://www.haomeiwen.com/subject/fazsaktx.html