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....)


粗知漏见,欢迎指正。

你可能感兴趣的:(Lua实现面向对象机制)