Lua面向对象编程

来源:互联网 发布:直男癌语录知乎 编辑:程序博客网 时间:2024/05/18 17:01

概述

Lua中的table本身就是一种对象

  • table和对象一样可以拥有状态、属性,以及对这些状态和属性的操作
  • table和对象一样拥有独立其值的标志(self)
  • table和对象一样具有独立于创建者的生命周期,换句话说,就是自己掌握自己的生命周期

但是,和C++、Java语言不一样,它并不存在显示的关键词(例如,Class)来描述一个类,所以我们只能利用table和元表机制来模拟类

Lua的面向对象是建立在元表和元方法这一机制上的,应当清楚的明白Lua的key-value的查询过程:

这里写图片描述

模拟类

最简单的定义类的方法,即直接使用Lua内置的table描述类的属性和行为

Account = {    balance = 100,    withdraw = function(v)        Account.balance = Account.balance - v    end}

如果拿C++来类比的话,看起来有点像如下形式

class Account{    public int balance = 100;    public void withdraw(int value){        balacne -= value;    }}   

不同的是在Lua中Account只是一个表,相当于一个对象,而C++中Account则是类,可以生成多个对象。
而且Lua的Account实现有很多值得吐槽的地方

  • 在函数withdraw中直接访问全局变量Account
  • 不能以Account为“模板”生成多个对象,一点都不符合类的概念

针对第一点,我们可以改成以下形式

function Account.withdraw(self, v)    self.balance = self.balance - vendlocal obj = Accountobj.withdraw(obj,20)

这样看起来也很别扭,于是加入了语法糖:,变成下列形式

function Account:withdraw(v)    self.balance = self.balance - vendlocal obj = Accountobj.withdraw(20)

接下来就要解决生成多个对象的问题了,其实也很简单,如果有一个名为Class的描述属性和操作的table,想让obj成为Class的对象(或者称让Class作为obj的原型),只需要:

setmetatable(obj, { __index = Class} )

即可

简单解释一下,在设定了元表之后,使用obj访问字段或者函数的时候,都会在Class这个表中寻找。(其实你可以发现,obj实际上是继承了Class)

为了更好的理解,给出一个比较完整的实例

Account = {    balance = 100,    withdraw = function(self, v)        self.balance = self.balance - v    end,    new = function(self,obj)        obj = obj or {}        setmetatable(obj, self)        self.__index = self        return obj    end}local o = Account:new()print(getmetatable(o))print(Account)for k,v in pairs(Account) do     print(string.format("%-15s %-15s", tostring(k), tostring(v)))end

结果如下:

这里写图片描述

对照代码,我们可以画出一个大概的模型图

这里写图片描述

obj是new出来的对象,它的元表是Account这个表(因为是用Account:new()所以self是Account本身),而Account中定义的__index方法是它本身。
这样,在调用obj:withdraw(10)时,实际上走的是以下这个流程:

getmetatable(obj).__index.withdraw(10)getmetatable(obj) = AccountAccount.__index = Account最后就相当于Account.withdrwa(10)

这里,我曾有个疑问,为什么要

setmetatable(obj,self)self.__index = self

而不是直接

setmetatable(obj, {__index = self})

这里有两个原因

  • 如果采用后者,每次new一个对象都要新建一个匿名的table,开销很大
  • 使用前者还可以更方便的实现单一继承

单继承

如果想从Account派生一个新的的类SpecialAccount,我们可以这样写:

SpecialAccount = Account:new( {limit = 1000} )

如果只这样写,那么和new一个Account的对象没有什么区别,如果你写下列代码你会发现

for k,v in pairs(SpecialAccount) do    print(string.format("%-15s %-15s", tostring(k), tostring(v)))end--输出结果是 limit 1000--只有属性limit

但是,如果你以SpecialAcoount为新的类new对象时,情况就变了

local sobj = SpecialAccount:new({})

SpecialAccount从Account那里继承了new,但是这次执行时,self是SpecialAccount
那么,sobj的元表是SpecialAccount,SpecialAccount的__index元方法仍然是SpecialAccount。

当执行sobj:withdraw(20)时,实际发生了下列过程:

  1. sobj中无withdraw字段,于是,getmetatable(sobj).__index.withdraw,也就是SpecialAccount中的withdraw
  2. 然而,SpecialAccount中也没有withdraw字段(只有limit字段),于是getmetatable(Special).__index.withdraw, 也就是Account中的withdrwa字段

这样,就实现了单一继承。SpecialAccount之所以特殊,是因为它可以重写从基类的继承的方法,仅需

function SpecialAccount:withdraw(value)    -- body    --do something differentend

最重要的是理解Lua的元表和元方法,理解Lua查找key的过程。

原创粉丝点击