【Lua】面向对象解析

写在前面

我们知道lua本身是不支持继承的,在lua中所有的对象都是由table组成的,这里我们都知道可以使用元表来模拟类的继承。其中核心实现就是用的__index元方法。简单的实现如下:

Super={}    --父类
SubClass={}    --子类
setmetatable(SubClass,{__index=Super})    --模拟继承

简单封装

我们先来看看一个简单的封装继承的方式。

BaseObject = {}
BaseObject .__index = BaseObject 

function BaseObject:New(object,...)
  object = object or {}
  setmetatable(object, self)
  self.__index = self
  if object.Ctor then
    object:Ctor(...)
  end
  return object
end

这里实现了一个简单的基类,并实现了一个简单的构造方法,可以传入值。但是个人觉得使用这个构造方法不是很好,还不如直接写一个方法在外面写一个初始化方法。

middleclass封装

这是一个github上面有人给出的一个lua继承的方案,特点就是简单实用。github地址,用的版本是4.1。

简单用例:

local class = require 'middleclass'

Person = class('Person') --声明一个Person类
function Person:initialize(name)
  self.name = name
end
function Person:speak()
  print('Hi, I am ' .. self.name ..'.')
end

AgedPerson = class('AgedPerson', Person) -- 声明一个继承于Person的AgedPerson类
AgedPerson.static.ADULT_AGE = 18 --设置一个变量
function AgedPerson:initialize(name, age)
  Person.initialize(self, name) -- 调用父类的构造方法
  self.age = age
end
function AgedPerson:speak()
  Person.speak(self) -- 打印 "Hi, I am xx."
  if(self.age < AgedPerson.ADULT_AGE) then --调用子类的一个变量
    print('I am underaged.')
  else
    print('I am an adult.')
  end
end

local p1 = AgedPerson:new('Billy the Kid', 13) -- 实例化对象
local p2 = AgedPerson:new('Luke Skywalker', 21)
p1:speak()
p2:speak()

输出:

Hi, I'm Billy the Kid.
I am underaged.
Hi, I'm Luke Skywalker.
I am an adult.

这个是官方给出的一个例子。非常简单容易的去理解。还有一些官方的教程在这里

解析:

我们从 class('Person')这一句开始解析嘛,正好可以看一下他的一个工作流程。


这里是调用的__call元方法,元方法中调用的middleclass.class(...),生成类的表。

setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })

middleclass.class(...)中调用了成生有继承的类super:subcalss与生成单独类的_includeMixin(_createClass(name), DefaultMixin),

_createClass(name)这个方法是用来创建一个类的基本结构或者说可以理解为一个抽象类:

local aClass = { name = name, super = super, static = {},
               __instanceDict = dict, __declaredMethods = {},
               subclasses = setmetatable({}, {__mode='k'})  }

属性有:

  • name 类名
  • super 父类的table
  • static 静态表
  • __instanceDict 存放类定义的属性和函数(包含父类)
  • __decaredMethod (当前类声明的方法,不包含父类)
  • subclasses 子类表, 使用弱引用

先设置aClass.static表的元表为 __index = __instanceDict(或者super.static),然后设置aClass元表__index 为 static以及__tostring与__call元方法。这里的__call元方法时调用的类中的new方法。

  if super then
    setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or super.static[k] end })
  else
    setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
  end

  setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,__call = _call, __newindex = _declareInstanceMethod })

使用_includeMixin这个方法为我们的类做一个具体的填充。这里使用到一个DefaultMixin的表。我们可以把这个表理解为来实现上面我们构造出来的抽象类。
DefaultMixin提供如下方法:

  • __tostring:默认tostring方法
  • initialize:初始化方法 类似于默认构造函数
  • isInstanceOf:判断是否是一个类的实例
    静态方法:
  • allocate:创建一个实例表
    a.添加class属性,指向类表
    b.将类表的__instanceDict设置为元表
allocate = function(self)
  assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
  return setmetatable({ class = self }, self.__instanceDict)
end,

●new:调用allocate与initialize方法,实例出来对象并调用构造方法。
●subclass:创建子类

subclass = function(self, name)
  assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
  assert(type(name) == "string", "You must provide a name(string) for your class")

  local subclass = _createClass(name, self)//创建一个子类的抽象类。

  //把父类实例中的所有方法赋给子类使用_propagateInstanceMethod方法。
  for methodName, f in pairs(self.__instanceDict) do
    _propagateInstanceMethod(subclass, methodName, f)
  end

  //设置子类的默认构造方法。
  subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
  
  //在自己的subclass索引中,添加子类的索引
  self.subclasses[subclass] = true
  //调用自己的subclassed方法,参数为子类
  self:subclassed(subclass)

  return subclass
end

●isSubclassOf:是否是指定类的子类。
●include:将参数合并到本类中,调用的_includeMixin方法。


_includeMixin:使用这个方法把填充之前构造的抽象类,分别把静态与非静态的方法与参数都赋值给抽象类。


_declareInstanceMethod:在类中添加声明方法和属性

local function _declareInstanceMethod(aClass, name, f)
  aClass.__declaredMethods[name] = f

  if f == nil and aClass.super then
    f = aClass.super.__instanceDict[name]
  end

  _propagateInstanceMethod(aClass, name, f)
end
  • 注册到__declaredMethods
  • 如果f为nil,则去父类取该字段
  • 将域添加到子类中

_propagateInstanceMethod:添加域到表中,并添加到所有的子类中,相当于继承

local function _propagateInstanceMethod(aClass, name, f)
  f = name == "__index" and _createIndexWrapper(aClass, f) or f
  aClass.__instanceDict[name] = f

  for subclass in pairs(aClass.subclasses) do
    if rawget(subclass.__declaredMethods, name) == nil then
      _propagateInstanceMethod(subclass, name, f)
    end
  end
end
  • 如果name = __index, 调用_createIndexWrapper
  • 将f添加到aClass.__instanceDict[name]中
  • 遍历所有子类,如果子类不包含该方法,则添加到子类中(若包含,相当于重写)

_createIndexWrapper:对__index处理


总结

  • __instanceDict 记录当前类,以及所有父类定义的实例方法(属性), __index指向自己
  • __declaredMethods 记录当前类声明的方法(属性)
  • subclasses 当前类的所有子类,弱引用
  • static 静态表,定义new, include, isSubclassOf等方法,__index指向__instanceDict。
    实例变量不能直接访问static表,必须通过.class.static访问。
    类的静态方法可以通过class.static:func 来定义
  • 类的默认表 定义__instanceDict,__declaredMethods, static等属性。
    __index指向static表,可以直接使用static中的字段(A:new())。
    __newIndex为_declareInstanceMethod方法,添加字段时会更新__instanceDict以及__declareMethods
  • 实例表 定义class属性,指向当前的类。 metatable为对应类的__instanceDict
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,835评论 6 13
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 1、Window上开发Rust,在编译的时侯,由于网络问题,下库crates时,基本会失败 解决方式: C:\Us...
    新高_Butland阅读 571评论 0 2