Lua实现面向对象机制

开篇废话

最近重拾了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....)


粗知漏见,欢迎指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • 定义类并创建实例 在Python中,类通过 class 关键字定义。以 Person 为例,定义一个Person类...
    绩重KF阅读 3,919评论 0 13
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,368评论 25 707
  • 文/施美风 去年秋日,携北方友人游览天平山,除了留给我们满目的红枫胜景外,印象颇深的还有园中的那一塘残荷。零零碎碎...
    与秋阅读 593评论 1 9
  • 在礁石的贝壳上作画, 我曾以此自诩。 当鸟划过蓝天, 那无形的轨迹 是怎样一种光辉? 是太阳,是月亮,是星星 他们...
    心种阅读 134评论 0 1