lua middleclass 学习记录

1. 创建类

local class = require "middleclass"
local parent = class("parent")
function middleclass.class(name, super)
  assert(type(name) == 'string', "A name (string) is needed for the new class")
  return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end

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

class调用通过middleclass的__call元方法调用到middleclass.class。在class函数中有两个不同的处理情况,如果传入父类,根据父类生成类模板,否则生成一个基类模板。

1.1 无基类情况

1.1.1 _createClass

无论是否有基类,首先都会通过_createClass函数创建类的基本模板:

local function _createClass(name, super)
  local dict = {}
  dict.__index = dict

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

  if super then
    setmetatable(aClass.static, {
      __index = function(_,k)
        local result = rawget(dict,k)
        if result == nil then
          return super.static[k]
        end
        return result
      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 })

  return aClass
end

函数首先创建了类模板aClass,包含六个字段

名称含义
name类名称
super基类模板
static类静态函数(包含allocate、new、subclass、subclassed、isSubclassOf、include)
__instanceDict实例属性table(包含实例化出的对象拥有的函数,字段等),该table的__index字段指向自己
__declaredMethods类自身声明的属性,不包括继承来的
subclasses子类列表,key为弱引用

之后设置aClass的static字段的原表,当触发原表索引时,无论是否有父类,首先在aClass的__instanceDict中查找目标,如果没有找到且有父类,则会继续在父类的static中继续查找,所有的上表里列出的static函数只有在最上层基类的static字段中存储,最主要的使用就是调用new函数,不过从代码上看是可以通过类模板直接调用成员函数

local child = require "child"
child:func1()

最后设置aClass的元表,元表的__index指向aClass的static方法,因此通过类模板调用函数可以通过上述路径进行查找;__call函数设置为一个调用new方法的函数,因此可以不使用new进行实例化:

local function _call(self, ...) return self:new(...) end
local child = require "child"
local c1 = child() -- 等同于 child:new()

__newindex指向_declareInstanceMethod函数,该函数会将该类声明时的函数或字段添加到aClass的__declaredMethods字段中,并通过_propagateInstanceMethod函数将该函数或字段写入aClass的__instanceDict字段并通过递归调用_propagateInstanceMethod函数传递给所有子类

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

在_propagateInstanceMethod中对子类进行传播时会先判断aClass的__declaredMethods是否已经有同名的函数或字段,如果有则不覆盖,这样就达到了子类同名函数或字段覆盖父类值的目的

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

从上面的代码可以看出,aClass中添加的所有新函数或字段都会存储到__instanceDict中。

1.1.2 _includeMixin & DefaultMixin

在类模板创建完成之后通过class函数通过调用_includeMixin函数将DefaultMixin中的字段拷贝到aClass对应字段里。首先先看DefaultMixin的内容:

local DefaultMixin = {
  __tostring   = function(self) return "instance of " .. tostring(self.class) end,

  initialize   = function(self, ...) end,

  isInstanceOf = function(self, aClass)
    return type(aClass) == 'table'
       and type(self) == 'table'
       and (self.class == aClass
            or type(self.class) == 'table'
            and type(self.class.isSubclassOf) == 'function'
            and self.class:isSubclassOf(aClass))
  end,

  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,

    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,

    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
        if not (methodName == "__index" and type(f) == "table") then
          _propagateInstanceMethod(subclass, methodName, f)
        end
      end
      subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end

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

      return subclass
    end,

    subclassed = function(self, other) end,

    isSubclassOf = function(self, other)
      return type(other)      == 'table' and
             type(self.super) == 'table' and
             ( self.super == other or self.super:isSubclassOf(other) )
    end,

    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
  }
}

DefaultMixin是一个table,包含了三个实例函数和一个静态函数列表。_includeMixin 函数主要会将这些不同的函数分别拷贝的aClass的不同字段里

local function _includeMixin(aClass, mixin)
  assert(type(mixin) == 'table', "mixin must be a table")

  for name,method in pairs(mixin) do
    if name ~= "included" and name ~= "static" then aClass[name] = method end
  end

  for name,method in pairs(mixin.static or {}) do
    aClass.static[name] = method
  end

  if type(mixin.included)=="function" then mixin:included(aClass) end
  return aClass
end

首先对于key不为"included"(或者"static"的值,会直接插入aClass中,由于aClass的__newindex指向_declareInstanceMethod函数,因此DefaultMixin中定义的非static函数都会被最终设置到aClass的__instanceDict中。static函数会被设置到aClass的static字段中。included没有找到相关的声明,不懂这个是啥。。。。

总结一下,在没有基类时,创建一个类模板,会首先创建一个aClass的table,包含名称、基类、字段、子类等信息,并设置该table的元表,元表的__index指向aClass的static字段,可以使用类模板调用静态函数;__call指向一个调用new的函数,可以直接通过类模板创建实例对象;__newindex指向一个函数,该函数把新增函数或字段都填入__instanceDict和__declaredMethods中。

通过断点看一下具体的内容:

在这里插入图片描述
当调用完成class方法,parent类应该只包含默认的信息:
在这里插入图片描述
再往下走一步,添加member1之后,可以看到__instanceDict和__declaredMethods中都加入了member1的数值
在这里插入图片描述

1.2 有基类情况

有基类的情况会调用基类的subclass函数

function middleclass.class(name, super)
  assert(type(name) == 'string', "A name (string) is needed for the new class")
  return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end

前文提到subclass是DefaultMixin中定义的一个static函数,因此通过类模板进行调用。函数首先通过_createClass创建一个模板,之后通过_propagateInstanceMethod将模板的__instanceDict除了table类型的__index之外的字段复制给子类模板,之后将子类的初始化函数设置为调用父类初始化函数的封装,并将子类模板注册给父类模板的subclasses字段中,并调用父类的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
    if not (methodName == "__index" and type(f) == "table") then
      _propagateInstanceMethod(subclass, methodName, f)
    end
  end
  subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end

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

  return subclass
end,

这里有个问题,父类向子类拷贝对象时是浅拷贝,因此像member2这种字段如果像测试代码中这么声明会导致所有的实例化对象引用的是同一个值

local child = require "child"

local c1 = child()
local c2 = child()

c1.member2.value = 30
c2.member2.value = 40
print(c1.member2.value, c2.member2.value)

上面代码的输出为
40 40
使用时要注意这个问题,可以在initialize函数中创建避免这个问题:

function parent:initialize()
    self.member3 = {
        value = 1
    }
    print("parent initialize")
end

2 实例化对象

根据前文所述,在调用new函数时,会调用到最顶层基类static中的new方法,也就是DefaultMixin中定义的new方法

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 = 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,

实例化的对象中只有一个class指向类模板,并将元表设置为类模板的__instanceDict字段,在用实例调用函数或者访问字段时访问的是__instanceDict对应的属性。

3 其他函数说明

3.1 isInstanceOf

该函数用来判断某个实例的类型是否是制定类模板,从函数实现上看也会判断实例的类型是否是继承自制定类模板

  isInstanceOf = function(self, aClass)
    return type(aClass) == 'table'
       and type(self) == 'table'
       and (self.class == aClass
            or type(self.class) == 'table'
            and type(self.class.isSubclassOf) == 'function'
            and self.class:isSubclassOf(aClass))
  end,
isSubclassOf = function(self, other)
      return type(other)      == 'table' and
             type(self.super) == 'table' and
             ( self.super == other or self.super:isSubclassOf(other) )
    end,

3.2 _createIndexWrapper

对类模板自定义的__index进行封装,没有用过,希望有大佬能帮说明一下

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

      if value ~= nil then
        return value
      else
        return (f(self, name))
      end
    end
  else -- if  type(f) == "table" then
    return function(self, name)
      local value = aClass.__instanceDict[name]

      if value ~= nil then
        return value
      else
        return f[name]
      end
    end
  end
end

3.3 include

DefaultMixin中定义的static函数,可以把其他table中的内容插入到类声明中

-- staticfunc.lua
return {
    extraFunc = function ()
        print("extraFunc")
    end,

    static = {
        extraStaticFunc = function ()
            print("extraStaticFunc")
        end,
    }
}
local class = require "middleclass"
local staticfunc = require "staticfunc"
local parent = class("parent") : include(staticfunc)
local child = require "child"

local c1 = child()
c1:extraFunc()
child:extraStaticFunc()

测试代码

-- parent.lua
local class = require "middleclass"
local staticfunc = require "staticfunc"
local parent = class("parent") : include(staticfunc)

parent.member1 = 1
parent.member2 = {
    value = 1
}

function parent:initialize()
    self.member3 = {
        value = 1
    }
    print("parent initialize")
end

function parent:func1()
    print("parent func1")
end

return parent
-- child.lua
local class = require "middleclass"
local parent = require "parent"
local child = class("child", parent)

function child:initialize()
    child.super.initialize(self)
    print("child initialize")
end

return child
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值