zoukankan      html  css  js  c++  java
  • SLua 中继承 C# 类接口 Slua.Class 的一个 Bug。

      由于目前要把大量的代码移植到 lua 中(真是够虐心的),面向对象肯定少不了,项目的代码都是这么设计的,于是就测试 Slua.Class 接口来扩展 C# 的类,发现有点问题,给作者提交了一个 Issue 和 一个 Pull Request,作者也很快确认并 Merge 了。

      问题是这样:当使用 Slua.Class 继承出来的类,实例化出来的所有实例都指向了最后一个实例,导致访问属性都是一样的。比如使用 main.txt 中得一段代码修改测试:

    -- test inherite class
    local mv = My2(1, 2, 3)
    local mv_2 = My2(4, 5, 6)      -- I add for test.
    mv:Normalize()
    mv_2:Normalize()       -- I add for test.
    print("mv norm:", mv.x, mv.y, mv.z)      -- I modified for test.
    print("mv_2 norm:", mv_2.x, mv_2.y, mv_2.z)      -- I add for test.
    mv:Set(10, 20, 30)      -- I modified for test.
    mv_2:Set(40, 50, 60)      -- I add for test.
    print("mv:", mv.x, mv.y, mv.z)      -- I add for test.
    print("mv_2:", mv_2.x, mv_2.y, mv_2.z)      -- I add for test.

    结果将输出如下:

    mv norm:    0.62469504755442    0.78086880944303    0.93704257133164
    mv_2 norm:    0.62469504755442    0.78086880944303    0.93704257133164
    mv:    40    50    60
    mv_2:    40    50    60

      在以上结果中,My2 的实例 my, my_2 构造的值是不同的,但输出相同的结果。看看 Slua.Class 的代码,在 Helper.cs 中:

    local getmetatable=getmetatable
    local function Class(base,static,instance)
        local mt = getmetatable(base)
        local class=static or {}
        setmetatable(class, 
            {
                __call=function(...)
                    local r = mt.__call(...)
                    local ret = instance or {}
                    ret.__base=r
                    local ret = setmetatable(ret,{
                        __index=function(t,k)
                            return r[k]
                        end,
                        __newindex=function(t,k,v)
                            r[k]=v
                        end,
                    })
                    return ret
                end,
            }
        )
        return class
    end
    return Class

      以上代码中,ret 是类的模板,用来为各个实例化对象提供方法和属性,不应该被构造时返回(而且上面每次构造都返回了相同的一个 ret),但是 ret 应该是大家 shaderd,构造返回的对象应该是一个新构造的对象,且 __index 为 ret,这样既能获取派生类的各种方法属性,又不会不小心修改 ret。

      同时,我做了如下的一些小修改:

    1. 可以直接使用派生类调用积累的静态成员方法,如基类 Base.ShowStatic(),那么派生类可以直接使用:Derived.ShowStatic();
    2. 增加了一个名为 ctor 的可选构造函数(这个借鉴了云风给出的 lua-oop 方案);
    3. 保持通过访问父类方法使用 __base,但注意不用使用这个来访问父类成员变量,因为当你第一次在派生类访问父类变量,会被复制到派生类,所以可能会访问到错误的数据,只有派生类的才是有效的。

      修改完的代码如下:

    local getmetatable = getmetatable
    local function Class(base,static,instance)
        local mt = getmetatable(base)
        local class = static or {}
        setmetatable(class, 
            {
                __index = base,
                __call = function(...)
                    local r = mt.__call(...)
                    local ret = instance or {}
                    local ins_ret = setmetatable(
                        {
                            __base = r,
                        },
                        {
                            __index = function(t, k)
                                local ret_field
                                ret_field = ret[k]
                                if nil == ret_field then
                                    ret_field = r[k]
                                end
                                t[k] = ret_field
                                return ret_field
                            end,
                        })
                    if ret.ctor then
                        ret.ctor(ins_ret, ...)
                    end
                    return ins_ret
                end,
            }
        )
        return class
    end
    return Class

      使用跟以前一样,但可以增加一个构造函数:

    MyVector3 = Slua.Class(Vector3,
    {
    },
    {
        -- This is optional.
        ctor = function(self)
            print("Do something...")
        end,
    })

      但是我觉得还是有点小问题,以上书写新的扩展类代码的时候不是太方便,不能分开单独写每个成员变量和函数,也可以墙纸分开,但命名上不太好看,于是我自己又做了如下修改:

    local getmetatable = getmetatable
    local function Class(base)
        local mt = getmetatable(base)
        local class = {}
        class.ctor = false
        setmetatable(class, 
            {
                __index = base,
                __call = function(...)
                    local r = mt.__call(...)
                    local ins_ret = {__base = r,}
                    setmetatable(ins_ret,
                        {
                            __index = function(t, k)
                                local ret_field
                                ret_field = rawget(class, k)
                                if nil == ret_field then
                                    ret_field = r[k]
                        if 'function' == type(ret_field) then
                                            class[k] = ret_field
                        else
                            ins_ret[k] = ret_field
                        end
                                end
                                return ret_field
                            end,
                        })
    
                    if class.ctor then
                        class.ctor(ins_ret, ...)
                    end
    
                    return ins_ret
                end,
            }
        )
        return class
    end
    return Class

    这样的话,我就可以更方便的定义类,符合以前的书写习惯,同时,优化一下,当访问派生类不存在的的父类成员时,之拷贝函数,不拷贝成员变量,以免浪费空间。这样我可以这样书写:

    MyVector3 = Slua.Class(Vector3)
    
    -- Constructor, optional.
    function MyVector3:ctor()
        print("Do something!")
    end
    
    -- Instance method.
    function MyVector3:Normaize()
        --Do your own normalize.
    end
    
    -- Static method.
    function MyVector3.PrintMyName
        print("MyVector3")
    end

      但作者说如果不是 bug,只是为了方便,最后这个不能修改,因为要考虑兼容性,已经有人这么用了,确实是这样,所以我就把这个提交到自己的另一个分支里,在自己的项目使用新方法。

     
     
  • 相关阅读:
    一个怂女婿的成长笔记【十八】
    一个怂女婿的成长笔记【十七】
    一个怂女婿的成长笔记【十六】
    一个怂女婿的成长笔记【十五】
    一个怂女婿的成长笔记【十四】
    一个怂女婿的成长笔记【十三】
    一个怂女婿的成长笔记【十二】
    一个怂女婿的成长笔记【十一】
    jmeter中“csv data set config ”函数的使用
    jmater中“_CSVRead”函数的使用
  • 原文地址:https://www.cnblogs.com/yaukey/p/4545093.html
Copyright © 2011-2022 走看看