写在前面
- 最近在琢磨“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。
- _class是什么?——> _class是记录各种类的类型(以下都称为“类-原型”)的词典(如果是我,会起名为_classTypeMap,这样更好懂)。
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