开篇废话
最近重拾了Lua,因为新项目在用。大概4-5年前准备进入从事游戏行的时候,看到某司的招聘简章中提到了Lua,借暑假的时间看完了《Lua程序设计(第2版)》。还记得开始的时候,看到交换变量用一条语句实现就莫名的兴奋,至今都没搞清楚当初为毛会兴奋...。书中对Lua语言做了比较详细的讲述,强调了其灵活性,然而当时处于似懂非懂的状态,没什么体会。工作一段时间再拿起它,确实产生了共鸣,现在越来越喜欢这小巧的Lua。
Lua的美在于:它不提供其他语言那么多丰富的特性,但是它提供了自己实现这些特性的机制。
今天要写的就是借助MetaTable,为Lua添加面向对象的特性。开始之前写上当年为之兴奋的一个行代码~~
a, b = b, a
1 代码
1.1 OO特性实现
此处的实现是从云风大神那里借鉴,简化产生的(Wiki原文)。由于个人资质有限,对稍复杂的方案理解起来比较吃力,虽然原版代码很短,但耗费了挺长时间才理解。为了以后使用方便,根据自己的思维方式,出了这一版自认为比较易于理解的实现。
-- 用于记录所有类结构
local _ClzTbl = {}
-- 索引基类成员的元表
local _BaseMeta = { __index = function(childClz, k)
if not childClz.base then return nil end
-- 变种点:1
-- 将基类中的成员拷贝到子类中,可以提高二次访问的效率(减少搜索的层次)
local v = childClz.base[k]
childClz[k] = v
return v;
end }
--- 定义类结构
--- 约定:构造函数使用名字Ctor
--- 注意:如果指定了基类base,则不能再为子类设置其他MetaTable.
---@param clzName string 类名,必须能全局唯一标识一个类
---@param base table 要继承的基类,没有则不传参
function class(clzName, base)
if type(clzName) ~= 'string' then return nil end
local clz = _ClzTbl[clzName]
-- 类结构已经定义过的,直接返回
-- 使用这种方式,避免在多次DoFile中被重复定义
if clz then return clz end
clz = {
Ctor = false,
base = base,
}
if base then
setmetatable(clz, _BaseMeta)
end
_ClzTbl[clzName] = clz
return clz
end
---变种点:2
---递归调用构造函数
---先调用基类的构造函数,再调用自己的
local _Create
_Create = function(clz, obj, ...)
if clz.base then
_Create(clz.base, obj, ...)
end
if clz.Ctor then
clz.Ctor(obj, ...)
end
end
--- 创建对象
---@param clz table 对象所属的类,可以是类型的table也可以是类型的名字
---@param ... 要传递给构造函数的参数列表
function new(clz, ...)
if type(clz) == 'string' then
clz = _ClzTbl[clz]
end
if not clz then return nil end
local obj = {}
setmetatable(obj, { __index = clz })
_Create(clz, obj, ...)
return obj
end
1.2 示例
-- 1.定义类
Base = class('Base')
function Base:Ctor(name)
self._name= name
print('This is Base....')
end
Child = class('Child', Base)
function Child:Ctor(name)
print('Old name:' .. self.__name)
self._name = name
print('New name:' .. self.__name)
end
-- 2.创建对象(两种方式是等价的)
child1 = new (Child, 'Walker')
child2 = new ('Child', 'Walker')
2 简介
此处实现,沿袭了C#这类本身支持OO的语言的使用习惯,也有Python的影子。Lua中类、对象没有任何区别,都是一个table而已。为了概念上的清晰,这里将其分开,视为不同的东西:class用于定义类结构,new用于创建对象。
2.1 class
用Table作为类结构,也就是生成对象的模板。
- 直接定义在类上的成员(函数、变量),都是在所有对象间共享的(此处和Python一样)
- 类结构中,用base变量引用一个基类结构,实现了单继承。
- 通过__index元方法,实现了高效的基类成员检索(继承)
2.2 new(对象)
- 直接用元表完成对象结构的创建
- _Create实现了C#的对象构造顺序(基类->子类)
- 在构造函数Ctor中对self赋值的变量,才是成员变量(Python方式)。当然由于动态语言的特性,在任意位置给self赋值都有效。
3 变种
细心的朋友可能发现了,实现代码中标记了两处变种点。这一版代码实现的特性和云风大神的基本一致,相当于用一个更易理解的方式重构了(至少对我自己更易理解)。顺带一提,目前项目中使用的就是云风大神的原版。重构完成再看的时候,发现这两处可以用不同的方式,支持其他情况或用法。
3.1 变种点1
变种点1是在调用到基类成员的时候,会将该成员拷贝到子类的Table中。这种方式提高了二次检索的效率,特别是继承层次较深的时候。这是一种空间换时间的方案,当内存资源比CPU资源更稀缺的时候,它就不合适了。并且通常继承层次都不会太深,在我看来Lua中继承超过3层,设计上就很可能有问题了。
这里的修改方式非常简单,直接__index=base就好了。(文件可以短10行了)
3.2 变种点2
_Create实现的对象构造层次,是从最顶层的基类,依次调用到当前子类,这是C#这类语言中使用的方式。后来想到了Python的方式,是否走基类的构造由子类决定。现在类结构中记录了base,删掉_Create直接调用Ctor就是Python方式。(文件又可以短10多行了Orz....)
粗知漏见,欢迎指正。