Lua middleclass 详解

A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support.
https://github.com/kikito/middleclass

函数功能解析

1. _createClasss(aClass, super)

返回默认的表

  local dict = {}
  dict.__index = dict

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

创建一个table,包含下列属性:

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

设置static表的元表为 __index = __instanceDict(或者super.static)

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

设置类的元表:__index 为 static, __tostring => 默认tostring,
_call =>调用static.new(), __newindex => _declareInstanceMethod

2. _includeMixin(aClass, mixin)

将一张表合并到另一张表中

  for name,method in pairs(mixin) do
    if name ~= "included" and name ~= "static" then aClass[name] = method end
  end
  • 除了"included"和"static",将mixin中的域全部赋值到aClass中(覆盖)
  for name,method in pairs(mixin.static or {}) do
    aClass.static[name] = method
  end
  • 将mixin.static中的域,赋值到aClass.static中
  if type(mixin.included)=="function" then mixin:included(aClass) end
  • 如果mixin包含included方法,则调用mixin的included方法,参数为aClass

3. DefaultMixin

** 默认合并表,创建类时自动添加到类中**
提供如下方法:

  • __tostring 默认tostring方法
  • initialize 初始化方法 类似于构造函数
 isInstanceOf = function(self, aClass)
    return type(aClass) == 'table' and (aClass == self.class or self.class:isSubclassOf(aClass))
  end,
  • isInstanceOf 判断是否是某个类的实例

为static添加方法:

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,
  • allocate 创建一个实例表
    • 添加class属性,指向类表
    • 将类表的__instanceDict设置为元表
new = function(self, ...)
      assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
      local instance = self:allocate()
      instance:initialize(...)
      return instance
    end,
  • new 通过allocate创建表,并调用initialize方法

  • subclassed 创建子类后的回调

 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)

      for methodName, f in pairs(self.__instanceDict) do
        _propagateInstanceMethod(subclass, methodName, f)
      end
      subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end

      self.subclasses[subclass] = true
      self:subclassed(subclass)

      return subclass
    end,

  • subclass 为类添加一个指定名字的子类,并返回子类
    • 创建指定名字的类,父类为自己
    • 将父类的__instanceDict中的所有域,复制到子类中
    • 子类的initialize 默认调用父类的initialize
    • 在自己的subclass索引中,添加子类的索引
    • 调用自己的subclassed方法,参数为子类
 isSubclassOf = function(self, other)
      return type(other)      == 'table' and
             type(self.super) == 'table' and
             ( self.super == other or self.super:isSubclassOf(other) )
    end,
  • isSubclassOf 是否是指定类的子类
  include = function(self, ...)
      assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
      for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
      return self
    end
  • include 将参数合并到本类中

4. _declareInstanceMethod(aClass, name, f)

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

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,则去父类取该字段
  • 将域添加到子类中

5. _propagateInstanceMethod(aClass, name, f)

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

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]中
  • 遍历所有子类,如果子类不包含该方法,则添加到子类中(若包含,相当于重写)

6. _createIndexWrapper(aClass, f)

对__index处理

local function _createIndexWrapper(aClass, f)
  if f == nil then
    return aClass.__instanceDict
  else
    return function(self, name)
      local value = aClass.__instanceDict[name]

      if value ~= nil then
        return value
      elseif type(f) == "function" then
        return (f(self, name))
      else
        return f[name]
      end
    end
  end
end
  • f为空,则返回aClass.__instanceDict
  • 如果__instanceDict包含name 则返回 <==> __index = __instanceDict
  • 如果f为函数, __index = f(self,name)
  • 否则,返回f[name]

7. _call(self, ...)

调用new方法,使A:new() <==> A()

8. _tostring(self)

** 默认tostring方法**

9. middleclass.class(name, super)

创建一个类,并设置父类

  • 如果super ~= nil 则调用subclass, 将那么添加到super的子类中
  • 否则,创建一张默认表,并将DefaultMixin合并进去

实例解析

Metamethods

不需设置metatable的元方法,直接在当前类中定义,即可实现原方法。
以tostring为例:

Point = class('Point')
function Point:initialize(x,y)
  self.x = x
  self.y = y
end
function Point:__tostring()
  return 'Point: [' .. tostring(self.x) .. ', ' .. tostring(self.y) .. ']'
end

p1 = Point(100, 200)
p2 = Point(35, -10)
print(p1)
print(p2)

Output:
Point: [100, 200]
Point: [35, -10]

  1. p1(实例)的__index为__instanceDict,所以调用tostring时,会去__instanceDict中去查找
  2. Pointer的__newindex为_declareInstanceMethod,在类中定义__tostring时,_declareInstanceMethod会将该方法加入到__instanceDict中
  3. 重写__tostring,相当于在自己的元表中定义该方法
  4. 其他元方法类似

Middleclass 4.x supports all the Lua metamethods. There are some restrictions:

  • Overriding the metatable metamethod is possible, but likely will give you trouble. Do this only if you know what you are doing.
  • Metamethods not supported by the Lua version being used are not supported. For example, the len metamethod does not work in Lua 5.1 or LuaJIT without 5.2 compatibility.

mixins

类变量提供include方法,可以将一个table的域拷贝到另一个table中,被拷贝的table会触发included方法

  1. class.static表中提供include方法
  2. class的__index指向class.static
  3. 被合并的table 调用include方法

Private stuff

在类内定义局部变量,或者局部函数,外部不可访问

lua的作用于限制

其他

class = require("middleclass")
A = class("A")
A.n = 1
B = A()
print(B.n)
B.n = 2
print(B.n)
print(A.n)

Output:
1
2
1

B是A的实例,B继承了n(B中没有n属性,则去__instanceDict中取)
当B对n赋值,则相当于在B表中添加属性,不会改变__instanceDict的值,再次获取该属性时,直接从B表中读,所以值为2,且不会改变A中的值

middleclass使用的表的简单总结

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 1,963评论 0 3
  • 「禅绕」春天+商陆根和商陆叶+米尔的尝试,用了暗线250,中间的小块弄的很糟糕,不过也不想涂黑,就那样吧,回头再试...
    五一的简书阅读 1,561评论 0 0