• Lua的面向对象


    Lua语言本身并没有提供面向对象的语法机制,这需要我们自己设计实现一套类的机制。首先,对于面向对象来说,我们至少需要类和对象这两个概念。同时,类至少包含一个用于构造对象的方法。对应到Lua上,就是一个代表类的table,它有一个构造函数,返回代表该类对象的table:

    Class = {}
    function Class:New()
        local o = {}
        return o
    end
    
    local inst = Class:New()
    

    另外,一个类可以定义若干的方法,该类的所有对象都可以调用这个方法。可是返回的对象table和类table实际上并无关联,如何让对象table索引到类table里定义的方法呢?答案是使用metatable。利用__index属性,当对象table找不到方法时,就会去索引类table里的方法:

    Class = {}
    function Class:New()
        local o = {}
        setmetatable(o, {__index = self})
        return o
    end
    function Class:A()
        print("hello world!")
    end
    local inst = Class:New()
    inst:A()
    

    运行可以看到hello world正常输出了。

    一个类除了定义方法以外,还可以定义一些成员变量,每个对象拥有各自的成员变量,是相互独立的。另外,我们不希望一个类对象可以随便定义新的成员变量或者成员函数。同样地,这里也是用到了metatable,__index属性可以让对象table去索引类table里的成员变量,利用__newindex属性,我们可以对要写入table的key进行检查,当对象想定义不在类table中存在的成员,或者覆盖类table中存在的函数时,可以进行报错提示;而覆盖已经存在于类的成员变量,则在对象table中写入一份新的副本,保证不同对象table的成员变量互相独立:

    Class = {}
    function Class:New()
        self.var = 1
        local o = {}
        setmetatable(o,
        {
            __index = self,
            __newindex = function(t, k, v)
                if self[k] then
                    if type(self[k]) ~= "function" then
                        rawset(t, k, v)
                    else
                        print("overriding function in object is forbidden")
                    end
                else
                    print("declaring new var/function in object is forbidden")
                end
            end
        })
        return o
    end
    function Class:A()
        print("hello world!")
    end
    local a = Class:New()
    local b = Class:New()
    print(a.var, b.var)
    a.var = 2
    print(a.var, b.var)
    b.var = 3
    print(a.var, b.var)
    a.newvar = 1
    b.A = function()
        print("object hello world")
    end
    print(a.newvar)
    b:A()
    

    运行输出的结果如下:

    既然有了成员变量,那么在面向对象的机制中,还有一个很重要的访问控制,即我们需要实现private机制,来使得某些成员变量或者成员函数只在这个类内部可见,对象引用它是不可见的。私有函数是比较好实现的,毕竟一个类的所有对象共享一个函数,我们只需要定义该函数为local function,对象就无法访问到它了:

    local function PrivateFunc(self)
        print("private func ", self.var)
    end
    
    function Class:B()
        PrivateFunc(self)
    end
    
    local a = Class:New()
    a:B()
    a:PrivateFunc()
    

    运行后会报错提示,找不到PrivateFunc这个函数调用,这个函数只能在类的内部进行调用,对象是访问不到的,也就实现了私有函数。

    但是对于私有变量来说,实现起来却比较困难,如果我们按照私有函数的方式来做,那么会出现类的所有对象都引用着同一个local变量,虽然对象无法访问到变量本身,但是这个变量一旦发生变化,将会影响到所有对象。它实际上变成了一个static的private变量,这个不是我们想要的。

    也许我们想说,可以通过操纵metatable的__index,来禁止对象外部访问私有变量。虽然这样行得通,但是同样的问题没有解决,即这个变量还是一个static变量。

    为了让每个对象都拥有各自的私有变量,我们只能将私有变量塞到Class:New()方法中。但是这就引发了另一个问题:就是这个私有变量定义之后,只在Class:New()方法中可见,其他方法也访问不到这个私有变量了。因此,我们还需要将类的其他成员函数的定义全部挪到New方法中,也就是说,从原先所有的对象通过metatable来共享一份成员函数,变成了所有的对象都拥有自己的成员函数:

    local Class = {}
    
    function Class:New()
        local o = {}
        o.var = 1
        local pvar = 1
    
        function o:A()
            print("hello world!")
        end
    
        function o:setPrivate(newval)
            pvar = newval
        end
    
        function o:getPrivate()
            return pvar
        end
    
        setmetatable(o,
        {
            __newindex = function(t, k, v)
                if self[k] then
                    if type(self[k]) ~= "function" then
                        rawset(t, k, v)
                    else
                        print("overriding function in object is forbidden")
                    end
                else
                    print("declaring new var/function in object is forbidden")
                end
            end
        })
    
        return o
    end
    
    local a = Class:New()
    local b = Class:New()
    a.pvar = 1
    b.pvar = 1
    a:setPrivate(3)
    b:setPrivate(2)
    print(a:getPrivate())
    print(b:getPrivate())
    

    输出如下:

    两个对象都无法直接访问到私有变量,而且它们各自拥有各自的私有变量,互不干扰。

    接下来,让我们考虑类的继承。由于我们现在实际上有两种实现方式,一种是使用metatable,实现的思路其实也比较直观,就是当类本身找不到方法或者变量时,就往它的父类寻找:

    local Class = {}
    function Class:New(base)
        self.var = 1
    
        setmetatable(self,
        {
            __index = base,
        })
    
        local o = {}
        setmetatable(o,
        {
            __index = self,
            __newindex = function(t, k, v)
                if self[k] then
                    if type(self[k]) ~= "function" then
                        rawset(t, k, v)
                    else
                        print("overriding function in object is forbidden")
                    end
                else
                    print("declaring new var/function in object is forbidden")
                end
            end
        })
        return o
    end
    
    function Class:A()
        print("hello world!")
    end
    
    local Extend = {}
    function Extend:New(base)
        -- 注意这里显式传入了self,因为需要的是扩展类信息而不是基类信息
        local o = base and base.New(self) or {}
        return o
    end
    
    function Extend:A()
        print("hello from extend")
    end
    

    一种是使用闭包,这种情况下,new出来的对象已经包含了这个类的所有信息,那就初始化的时候直接构造的是基类对象而不是空table即可:

    local Extend = {}
    function Extend:New(base)
        local o = base and base:New() or {}
        function o:A()
            print("hello from extend")
        end
        return o
    end
    

    以上讨论的是单继承的情况,那么多继承呢?对于使用metatable的方式,比较简单,直接修改__index方法,让其遍历所有基类,直到查找到第一个符合的为止:

    function CreateClass(...)
        local class = {}
    
        local baseList = { ... }
        setmetatable(class,
        {
            __index = function(_, k)
                for _, base in ipairs(baseList) do
                    local v = base[k]
                    if v then
                        return v
                    end
                end
            end,
        })
    
        function class:New()
            local o = {}
            setmetatable(o,
            {
                __index = self,
                __newindex = function(t, k, v)
                    if self[k] then
                        if type(self[k]) ~= "function" then
                            rawset(t, k, v)
                        else
                            print("overriding function in object is forbidden")
                        end
                    else
                        print("declaring new var/function in object is forbidden")
                    end
                end
            })
            return o
        end
    
        return class
    end
    
    local Base1 = CreateClass()
    function Base1:A()
        print("hello world from base 1")
    end
    
    local Base2 = CreateClass()
    function Base2:B()
        print("hello world from base 2")
    end
    
    local Base3 = CreateClass(Base1, Base2)
    
    local a = Base3:New()
    a:A()
    a:B()
    

    对于使用闭包的方式,却有点麻烦,因为这种方式类本身是不包含任何具体信息的,需要在调用New方法的时候,先构造出基类对象,将对象里的信息复制填充;不仅如此,由于我们要抽象出一个通用方法,定义类具体的方法,成员只能通过外部参数的形式进行传递:

    function CreateClass(template, ...)
        local class = {}
        local baseList = { ... }
    
        function class:New()
            local o = {}
            for _, base in ipairs(baseList) do
                local bo = base:New()
                for k, v in pairs(bo) do
                    if not o[k] then
                        o[k] = v
                    end
                end
            end
        
            for k, v in pairs(template) do
                o[k] = v
            end
        
            setmetatable(o,
            {
                __newindex = function(t, k, v)
                    if self[k] then
                        if type(self[k]) ~= "function" then
                            rawset(t, k, v)
                        else
                            print("overriding function in object is forbidden")
                        end
                    else
                        print("declaring new var/function in object is forbidden")
                    end
                end
            })
        
            return o
        end
    
        return class
    end
    
    local Base1 = CreateClass({ A = function() print("hello world from base 1") end })
    local Base2 = CreateClass({ B = function() print("hello world from base 2") end })
    local Base3 = CreateClass({}, Base1, Base2)
    
    local a = Base3:New()
    a:A()
    a:B()
    

    这种方式,用起来其实有点复杂了。

    云风在博客中提到了另外一种lua实现面向对象的做法:

    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
    

    这里有个小小的优化,就是把访问过的字段缓存到vtbl中,避免频繁调用元方法。

    如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

  • 相关阅读:
    进行编译时提示'error: unrecognized command line option "-std=gnu11"'如何处理?
    linux shell中如何批量添加一行内容到某些文件的末尾?
    linux内核中的电源管理接口
    linux内核中i2c驱动中slave模式接口的调用
    git clone时报错“Failed to connect to 127.0.0.1 port 2453: Connection refused”如何处理?
    第 8 章 输入框和导航组件
    第 7 章 图标菜单按钮组件
    第 6 章 辅组类和响应式工具
    第 5 章 栅格系统
    第 4 章 表单和图片
  • 原文地址:https://www.cnblogs.com/back-to-the-past/p/14783348.html
Copyright © 2020-2023  润新知