Lua面向对象设计基类

前言

面向对象在前文已经谈过,虽然Lua是一个脚本语言,本就不是为了面向对象而设计的。但是它的元方法可以非常优美的让我们设计出类之间的继承关系,单继承、多继承等。

本文就是简单的剖析一下,云风大佬早在十多年前对于Lua面向对象的设计。

继承的形式

  1. 通过元表指向父类,每次查找不存在的字段时,层层向上查找。
  2. 通过元表指向父类,每次查找就clone父类的所有字段到当前元表中。
  3. 通过元表指向父类,当查找不存在的字段时,将该字段clone到当前元表,下一次就可以直接返回。

基类的构造

这里采用的是第三种形式,参考云风大佬的源码如下:

#!/usr/local/bin/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(cls, ...) 
                if cls.super then 
                    create(cls.super, ...)
                end
                if cls.ctor then 
                    cls.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

源码剖析

首先简述一下每个变量结构的大体含义:

  1. _class:全局表,用于存储不同类的内存指针,指向vtbl
  2. super:当前类的继承父类。
  3. class_type:最终构造出的类,设置了ctorsupernew,以及元表__newindex属性。
  4. ctor:构造函数,默认false
  5. super:本类的父类。
  6. new:实例化函数,用于实例化对象。
  7. obj:实例化的对象的表,会设置该表的元表__index属性,为全局表_class索引的本类所对应的表vtbl
  8. create:默认有父类就先构造父类的原则,由上而下的构造ctor
  9. vtbl:(Virtual Table)本类用于在全局表_class索引的表,用于存储新增的属性、方法等(见第3点)。

还有没谈到的最重要的一点,也就是最后的一个代码段if super then:

  • 这里就是设置vtbl的元表,其元表的__index索引的是一个function,当字段找不到时,并且super存在时,去拿到存储在全局表_classsuper索引的表中该字段对应的值,并且取出后存储在本类的vtbl表中。

使用范例

#!/usr/local/bin/lua

require "OO" -- OO.lua 就是上面的那个基类

local People = class() 

function People:ctor(who, name, age) 
    print("class People ctor")
    self.who = who 
    self.name = name 
    self.age = age
end

function People:print_info()
    print(self.who, self.name, self.age)
end

function People:say() 
    print(string.format("%s say hello", self.who))
end

-----------------------------------------------------------------

local Student = class(People)

function Student:ctor(who, name, age)
    print("class stu ctor")
    self.who, self.name, self.age = who, name, age
end

function Student:say() 
    print(string.format("%s say 'HAPPY'", self.who))
end

people = People.new("people", "Cauchy", 19)
people:say()
people:print_info()
print()

student = Student.new("student", "AQ_Scott", 20)
student:say()
student:print_info()

输出

class People ctor
people say hello
people	Cauchy	19

class People ctor
class stu ctor
student say 'HAPPY'
student	AQ_Scott	20

分析:

  1. local People = class(),返回 class_type{},设置 class_type{} 的元表的__newindex属性,一个function去对应的 vtbl{} 中更新值,vtbl{} 相当于存放了 People 的字段和方法,并且全局表 _class{} 记录 class_type{} 作为索引指向的其 vtbl{}。
  2. people = People.new(…) 实例化的时候,返回 obj{},create去构造 People的父类,由于 People 不存在 super,我们设置了 Peoplector方法,所以调用该方法。注意 这里是 cls.ctor去调用,第一个参数是 obj{},而People:ctor(),所以 obj{} 作为 self 传入,设置好了对应的 ... 参数。并且 obj{} 的元表的__index索引到全局表 _class[People],即为 vtbl{}。而 vtbl{} 存放了 People 的字段方法,所以 people 可以获取到 People 的字段和方法。

  1. local Student = class(People),也是同理创建,不过多了 super属性为PeopleStudent 的元表的 __newindex对自己对应的 vtbl{} 中找字段,如果不存在,会去 vtbl{} 的元表的 __index查找,即去 People 的 vtbl{} 中取值,同时值赋给 Student 的 vtbl{}。

  2. Student 实例化 new 出来的对象 student,元表的__index指向全局表 _class[Student],即为 vtbl{}。


从输出可以看到,在实例化 student 的时候,先调用了 People 的构造函数。print_info 方法也能够正常执行,并且 say 方法也成功重写了。

还存在的疑惑:
实例化多个对象的时候,用的基类的表 vtbl{} 不是同一个吗?


update:2023年5月10日

所有通过class函数定义的类都会共享一个vtbl表。这个表用于存储每个类的成员变量和成员方法。当我们创建一个类的实例时,它的元表会被设置为对应类的vtbl表。因此,所有该类的实例都会共享同一个vtbl表。

在实践中,这并不会造成问题,因为每个类的成员变量和成员方法都是独立的,并且每个实例都有自己的成员变量和成员方法。虽然它们共享同一个vtbl表,但是通过元表机制,每个实例都能够正确地访问到自己的成员变量和成员方法。

如果需要为不同的对象分别存储数据,可以将数据存储在实例本身的属性中,而不是存储在vtbl表中。

总结:

  1. 这个框架提供了一个class函数,用于定义类和实现继承,同时也提供了一个new方法,用于创建类的实例。
  2. 在这段代码中,_class是一个全局表,用于存储所有已定义的类及其对应的虚拟函数表(vtbl)。class函数接收一个参数super,表示父类。如果没有父类,则supernil
  3. 首先,在class_type表中定义了三个属性:ctorsupernew。其中,ctor表示构造函数,super表示父类,new则是用于创建类实例的方法。new方法通过递归调用父类的构造函数来完成继承,并调用当前类的构造函数进行初始化。最后,使用setmetatable将实例的元表设置为当前类的虚拟函数表,以便实现成员变量和成员方法的访问。
  4. 接下来,创建了一个名为vtbl的空表,并将其关联到当前类上。同时,通过设置元表,实现了继承功能。如果当前类有父类,那么在访问当前类的成员变量和成员方法时,会先从父类中查找,如果父类中存在相应的成员,则将其复制到当前类的虚拟函数表中,并返回该成员的值。
  5. 最后,class函数返回当前类class_type,以便在创建类实例时使用。

这段代码实现了一个简单的面向对象编程框架,可以用于定义类、实现继承和创建类实例。它的实现原理是通过Lua的元表机制来实现成员变量和成员方法的访问,以及继承和多态性的实现。


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ღCauchyོꦿ࿐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值