原文链接:https://www.jianshu.com/p/fefe11d4544e
Lua元表使用 中的__index元方法可以实现面向对象和继承关系:
lua中没有类的概念,只有table,但可以用__index模拟类和对象:
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实现继承关系:
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和元表的一系列操作)
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方法中的设置元表)
function Object:__call(...) local object = setmetatable({}, self) object:new(...) return object end
上面Student的例子可以写成:
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访问、操作符行为
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
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
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 本身 这样一直包装下去就实现了类继承和覆盖
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句