Lua实现继承

2019-12-02 13:45:22 浏览数 (1)

原文链接:https://www.jianshu.com/p/fefe11d4544e

Lua元表使用 中的__index元方法可以实现面向对象和继承关系:

lua中没有类的概念,只有table,但可以用__index模拟类和对象:

代码语言:javascript复制
local A = {}

function A:new(name)
    self.__index = self
    
    return setmetatable({ 
        name = name 
    }, self)
end

function A:print()
    print("name is ", self.name)
end

local a = A:new("Tony")
a:print()
-- output: name is Tony

A和a两个表之间的关联是这样的:

这里name是表a的key,print是表A的函数,当用a调用print时,找到的元表A中的__index(指向A自己)中的print方法,而方法内的self是调用者a,所以self.name是a的name。 (Lua中的self)

两个类之间同样可以用__index实现继承关系:

代码语言:javascript复制
local Person = {}
function Person:new(name)
    self.__index = self
    return setmetatable({
        name = name
    }, self)
end
function Person:print()
    print("name is ", self.name)
end

local Student = setmetatable({}, Person)
Student.super = Person
function Student:new(name, score)
    self.__index = self

    local student = self.super:new(name)
    student.score = score

    setmetatable(student, self)
    return student
end
function Student:print()
    self.super.print(self)
    print("score is ", self.score)
end


local s = Student:new("Tony", 98)
s:print()

-- output :
-- name is Tony
-- score is 98

用图表示这三个table的关系:

在调用s:print()时,由于s没有print这个函数,找到的s的元表Student的__index(指向Student自己)中的print函数,

首先执行self.super.print(self),这里self是调用者s,s没有super这个属性,同样是找到元表Student中的super即Person,执行Person的print打出name is Tony(这里用点调用函数传入的第一个参数self还是s(Lua中的self)

然后第二行打印score即s.score;

当继承关系比较复杂时,这种调用显得比较混乱且容易出问题,可以封装一个Object基类,实现继承关系链,方便方法调用且减少出问题的几率。

实现面向对象的Object基类:

将设置__index和setmetatable的操作统一写在Object类里,方便使用和减少出错,一共有两处:

实现继承关系时:在Object的方法中实现继承关系(设置__index和元表的一系列操作)

代码语言:javascript复制
local Object = {}
Object.__index = Object
function Object:new()
end

function Object:extend()
    local SubClass = {}
    for k, v in pairs(self) do
        if k:find("__") == 1 then
            SubClass[k] = v
        end
    end
    SubClass.__index = SubClass
    SubClass.super = self
    setmetatable(SubClass, self)
    return SubClass
end

创建实例对象时:用__call实现构造方法(new方法中的设置元表)

代码语言:javascript复制
function Object:__call(...)
    local object = setmetatable({}, self)
    object:new(...)
    return object
end

上面Student的例子可以写成:

代码语言:javascript复制
local Person = Object:extend()
function Person:new(name)
    Person.super.new(self)
    self.name = name
end
function Person:print()
    print("name is ", self.name)
end

local Student = Person:extend()
function Student:new(name, score)
    Student.super.new(self, name)
    self.score = score
end
function Student:print()
    Student.super.print(self)
    print("score is ", self.score)
end

local s = Student("Tony", 98)
s:print()
-- output :
-- name is Tony
-- score is 98

ps. 要注意的是,在子类调用父类方法时,尽量都是用 ClassName.super 而不要用self.super,因为lua里的self是不确定的。(当传入self调用的父类方法中也有self.super时会进入死循环) metatable:表示文件的元表 元表里记录了 函数、table访问、操作符行为

代码语言:javascript复制
local Base = {
--定义要使用的成员变量
_arrData = {}
}

function Base:new(oMataTable )
    --实例化后的对象
    oMataTable = oMataTable or {}

    --将Base的元表 也就是函数说明、操作符、table访问都复制到新对象上 
    --简单的解释就是 oMataTable 表有了Base的所有函数和table操作
    setmetatable(oMataTable, self)

    --这句很重要 因为self会根据table自身变化
    self.__index = self

    return oMataTable
end

function Base:SetData(key, value)
    self.arrData[key] = value
    print(key, self.arrData[key])
end

function Child:GetData(key)
    return self.arrData[key]
end 

return Base
代码语言:javascript复制
local Child = {
--定义要使用的成员变量
_iData = 11
}

--这句作用就是将Child定义的成员保留下来_iData 然后再继承
Child = require("class.Base"):new(Child)

--覆盖基类函数
function Child:SetData(key, value)
    self.arrData[key] = value + self._iData 
    print(key, self.arrData[key])
end

return Child
代码语言:javascript复制
function TestClass()
    local base = require("class.Base"):new()
    base:SetData(1, 10)
    --输出 1,10
    local childTest = require("class.Child"):new()
    childTest :SetData(1, 10)
    --输出 1,21 父类方法被覆盖了
    print(childTest :GetData(1))
    --输出 20 子类继承了父类方法
end

分析: childTest 又是由Child创建而来,local childTest = require(“class.Child”):new() 这个时候内部的 self 代表的又是 Child 本身而并不是 Base self.__index = self 就指明访问表数据的时候查找 Child 本身 这样一直包装下去就实现了类继承和覆盖

0 人点赞