导航

关于Lua中的面向对象实现

Posted on 2021-06-30 10:51  Caiger  阅读(436)  评论(1编辑  收藏  举报

写在前面

  • 最近在琢磨“Lua热重载”,在测试中发现我之前对Lua中的面向对象实现有一些理解发生变化,这里记录一下。
  • 本文提到的面向对象实现来自云风

类实现

  • 《Lua程序设计(第4版)》以银行账户存取钱为例,解释了如何实现一个类。从“面向对象的3大特性”角度,它的实现概括如下:
    • 继承:改__index引向自己
    • 多态:不用新建其他类,会自动找到对应方法(多重继承,书上举例是用查找父类方法实现的)
    • 封装:
      • 单方法:将公开的字段和函数放在单独表中;不过这种方式不能“继承”。
      • 对偶:把表当key存起来;可“继承”。
  • 不过《Lua程序设计(第4版)》书上没有完整的可直接拷走的代码,我工作项目用的是toLua,toLua也没有提供Lua类脚本,所以用了云风设计的class代码,如下:
local _class={}
 
function class(super)
    local class_type={}
    class_type.ctor=false
    class_type.super=super
    class_type.new=function(...) 
            local obj={}
            do
                local create
                create = function(c,...)
                    if c.super then
                        create(c.super,...)
                    end
                    if c.ctor then
                        c.ctor(obj,...)
                    end
                end
 
                create(class_type,...)
            end
            setmetatable(obj,{ __index=_class[class_type] })
            return obj
        end
    local vtbl={}
    _class[class_type]=vtbl
 
    setmetatable(class_type,{__newindex=
        function(t,k,v)
            vtbl[k]=v
        end
    })
 
    if super then
        setmetatable(vtbl,{__index=
            function(t,k)
                local ret=_class[super][k]
                vtbl[k]=ret
                return ret
            end
        })
    end
 
    return class_type
end

理解

  • 对比:有对比才能看出云风写的class好在哪,之前我在看LuaFramework_UGUI框架时,它有一个写的很短很简单的LuaClass(如下),拿LuaClass和云风的对比,能看得出来LuaClass在“面向对象的3大特性”上没有实现“多态”和“封装”,LuaClass仅能作为参考,是不能直接搬到正式工程里用的。
--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

--endregion
  • 回到class,最开始阅读class代码时,我有好些地方没看懂,包括“_class是放什么的”,“class_type为什么要起名叫class_type”等等。最近在琢磨“Lua热重载”时,再次回顾了这里,对以前理解错误的地方或是一知半解的问题有了新回答。以下是我在阅读时冒出的问题和对应回答:
    • _class是什么?——> _class是记录各种类的类型(以下都称为“类-原型”)的词典(如果是我,会起名为_classTypeMap,这样更好懂)。
      • 比如:新建两个类-原型A和B,此时_class中会记有类-原型A和类-原型B。
local A = class()
local B = class()     
    • class()到底做了什么?——> class()其实负责的是创建“类-原型”,而非“类-实例”,这从它最后return的值是class_type可以看出。如果需要创建“类-实例”,就得用new函数。
    • 下面的代码是什么意思?——> vtbl 即value table,setmetatable这里对class_type有写操作,却无读操作,即class_type里的数据是只写属性,即这些数据仅限于类内部使用。
local vtbl={}
_class[class_type]=vtbl

setmetatable(class_type,{__newindex=
    function(t,k,v)
        vtbl[k]=v
    end
})

    有需要的话,可以修改,例如下方就是把原来的只写属性改为可读可写属性,即public:

local vtbl = {}
_class[class_type] = vtbl

setmetatable(class_type, {
    __newindex = function(t,k,v)
        vtbl[k] = v
    end,
    
    __index = vtbl,
})
    • 下面的代码是什么意思?——> 查找自己没有的数据时,如果有父类super,先去_class里找类-原型super的数据,拿该数据覆盖/填充
if super then
    setmetatable(vtbl,{__index=
        function(t,k)
            local ret=_class[super][k]
            vtbl[k]=ret
            return ret
        end
    })
end

 

  • 根据上面回答,以下是我加了注释的class代码:
-- https://blog.codingnow.com/cloud/LuaOO
-- 类(注释版)

-- _class是记录各种类的类型(以下都称为“类-原型”)的词典
-- 比如:local A = class(),local B = class(),则_class中会记有类-原型A和类-原型B
-- 如果是我,会起名为_classTypeMap,这样更好懂
local _class = {}

function class(super)
    -- 创建的是类-原型class_type,只有用了类-原型的new函数,才能得到类-实例
    local class_type = {} 
    -- 自定义构造函数ctor    
    class_type.ctor = false
    class_type.super = super
    class_type.new = function(...)
        local obj = {}
        do
            local create = function(c, super)
                if c.super then
                    create(c.super, ...)                    
                end
                if c.ctor then
                    c.ctor(obj, ...)
                end
            end
            create(class_type, ...)            
        end
        -- 在_class中查找类-类型为class_type的数据
        setmetatable(obj, {__index = _class[class_type]})
        return obj
    end
    
    -- vtbl即value table
    local vtbl = {}
    _class[class_type] = vtbl
    -- 对class_type有写操作,却无读操作,即class_type里的数据是只写属性,即这些数据仅限于类内部使用
    -- (可见《Lua程序程序设计(第4版)》第21章“面向对象编程”的“对偶”)
    setmetatable(class_type, 
        {
            __newindex = function(t, k, v)
                vtbl[k] = v
            end
            
        })
    
    -- 查找自己没有的数据时,如果有父类super,先去_class里找类-原型super的数据,拿该数据覆盖/填充
    if super then
        setmetatable(vtbl, 
            {
                __index = function(t, k)                    
                    local ret = _class[super][k]
                    vtbl[k] = ret                    
                    return ret
                end
                
            })    
    end
    
    return class_type
        
end