美文网首页征服Unity3d
【Lua】面向对象解析

【Lua】面向对象解析

作者: 黒可乐 | 来源:发表于2017-10-26 10:45 被阅读0次

写在前面

我们知道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

相关文章

  • 【Lua】面向对象解析

    写在前面 我们知道lua本身是不支持继承的,在lua中所有的对象都是由table组成的,这里我们都知道可以使用元表...

  • 面向对象

    面向过程与面向对象: 内存解析 对象数组的内存解析 匿名对象

  • 大话C与Lua(五) 面向对象的数据结构——userdata

    如何实现面向对象? 熟悉Lua的同学都知道!在Lua内部已经实现了面向对象的基本机制(table), 同时也为宿主...

  • Lua面向对象

    面向对象特征1) 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。2) 继承:继承的方法允许...

  • Lua 面向对象

    面向对象编程(Object Oriented Programming,OOP)是一种非常流行的计算机编程架构。 以...

  • Lua面向对象

    面向对象有三个要点: 1. 对象拥有状态; 2. 对象拥有独立的标志; 3. 对象有独立的生命周期。 在Lua语言...

  • 非常非常详细的Lua面向对象(二)——冒号,点号以及self

    前言 在非常非常详细的Lua面向对象(一)——元表与元方法中说完了一些基础概念,这些是我们使用Lua模拟面向对象的...

  • Lua面向对象编程

    在lua原生语法特性中是不具备面向对象设计的特性。因此,要想在lua上像其他高级语言一样使用面向对象的设计方法有以...

  • Lua面向对象实现

    这个类主要是把基类和派生类绑定起来,并且调用ctor构造函数用法如下 注意调用父类的方法要用"."别用":"是因为...

  • lua的面向对象

    直接贴代码,参考的也是别人的,只不过其中几点,增加一点自己的理解http://blog.sina.com.cn/s...

网友评论

    本文标题:【Lua】面向对象解析

    本文链接:https://www.haomeiwen.com/subject/xpawyxtx.html