lua面向对象详解(3)

三、lua面向对象

1、lua中的self

首先来看一个例子:

localAccount = {balance = 1000}

functionAccount.Withdraw(money)

   Account.balance = Account.balance - money

end

 

localaa = Account;

aa.Withdraw(100)

print(aa.balance)        --900

print(Account.balance)   --900

 

Account= nil

--aa.Withdraw(100)  --error : attempt to index upvalue 'Account'(a nil value)

当定义aa时,aa实际上是Account的一个“引用”,并不会实际的给aa分配该table的一个拷贝,类似于linux的硬链接或windows的内核对象,执行了Account= nil 之后,实际上这个table还在,只是Account对这个table的链接断裂,我们可以通过输出的两个值可以看出。

把Account 置为 nil 之后再调用Withdraw 函数,由于函数内使用了Account对象,所以会引起访问出错。

在看一个改进版本:

localAccount = {balance = 1000}

functionAccount.Withdraw(self,money)

   self.balance = self.balance - money

end

 

localaa = Account

Account= nil

aa.Withdraw(aa, 100)

print(aa.balance)

aa:Withdraw(100)

print(aa.balance)

在这个版本中,调用Withdraw函数时,使用的是传入函数的self的balance值,这个self现在就是aa的“引用”了。Lua中提供了一个语法糖,使用冒号调用函数时,会把调用对象作为第一个参数传入函数。

请注意函数书写的三种方式:

(1)    table中写函数,需要指定self参数:

local Account =

{

       Withdraw = function(self , money)

       end,

}

(2)    函数外部写函数,不指定self参数:

local Account = {}

functionAccount:Withdraw(money)

end

(3)函数外部写函数,指定self参数

local Account = {}

functionAccount.Withdraw(self , money)

end

虽然这里访问可以了,但是,这并不是面向对象,因为对aa中balance的修改实际上就是对Account中的balance修改。这里主要介绍self的用法,self其实与c++中的this指针有相同的作用。

2、继承

Lua中没有类的概念,对象是没有类型的,但是我们可以为一些对象构建相同的共有的行为,这就是原型,不过我们要模拟类也是可以的。由于原型与类在这里的作用相同,这里仍采用类的叫法。

要在lua中实现原型,可以用继承的方法,这里用到了setmetatable方法。

setmetatable(a, {__index = b})

这样就能让b作为a的原型,当要访问a中的某个字段或函数不存在时,就会在b中查找。

我们再来看一下另一个版本:

localAccountClass =

{

   balance = 1000,

   New = function(self , obj)

      obj = obj or {}

      setmetatable(obj , self)

      self.__index = self

      return obj

   end,

   Withdraw = function(self , money)

      self.balance = self.balance - money

   end,

}

 

localaa = AccountClass:New()

localbb = AccountClass:New()

aa:Withdraw(100)

print(aa.balance)     --900

print(bb.balance)     --1000

print(AccountClass.balance)    --1000

这里多了一个New函数,用于生成以AccountClass为原型的对象。当调用

aa:Withdraw(100)

时,会发生什么?首先lua会在aa中查找Withdraw函数,可以看到New函数中生成的aa只不过是一个空的table,并没有这个函数。于是lua就会查找aa是否具有元表,如果有,就继续在元表中查找该方法:

getmetatable(aa).__index.Withdraw(aa,100)

于是就有

AccountClass.Withdraw(aa,100)

这样就调用了AccountClass的方法,但传入的self对象是aa自己,而不是AccountClass。

注意,这里还有一个很有趣的东西:我们知道aa并没有balance字段,那么如果使用aa.balance会不会修改AccountClass对象的balance字段的值呢?我们把过程分解一下:

aa.balance= aa.balance – money

调用时,会先计算等号右边,lua没有在aa中找到balance字段,就会在其元表中查找,转换为:

aa.balance= getmetatable(aa).__index.balance - money

这其实是一个赋值的过程,这个时候会在aa中生成一个balance字段,值为等号右边的值,这样,aa就继承了AccountClass中的值和方法。

通过上面的实验也可以看出,由AccountClass对象New出来的对象都有自己的字段值和相同的方法,我们称这些对象继承自AccountClass。

3、多态

我们先看下面的例子:

localAccount = {

   balance = 1000,

   New = function(self , obj)

      obj = obj or {}

      setmetatable(obj , self)

      self.__index = self

      return obj

   end,

   Withdraw = function(self , money)

      self.balance = self.balance - money

   end,

   Deposit = function(self , money)

      self.balance = self.balance + money

   end,

}

 

localSpecialAccount = Account:New()

functionSpecialAccount:Withdraw(money)

   self.balance = self.balance - money - 5

end

 

localaa = SpecialAccount:New()

aa:Withdraw(100)

print(aa.balance)     --895

aa:Deposit(100)

print(aa.balance)     --995

这里,Account定义了两个方法,Withdraw和Deposit,我们用这个对象生成对象SpecialAccount。在lua中对象可以生成对象。SpecialAccount定义了自己的Withdraw方法,用SpecialAccount生成对象aa。当对aa进行操作时,lua首先查找aa是否有需要的内容,没有则会到SpecialAccount中查找(注意,生成aa时setmetatable传入的是SpecialAccount,而不是Account),如果没有再到SpecialAccount的元表Account中查找。

基于lua的这种查找顺序,如果调用Withdraw方法在SpecialAccount中查找到了,就不会再到Account去查找。所以,SpecialAccount定义了自己的Withdraw方法,则其生成的对象就会用这个方法,

由于lua中没有类型,不存在向c++中那样用基类的引用或指针来指向派生类,所以这里只能说由Account生成的使用对象会调用Account的Withdraw方法,而SpecialAccount生成的使用对象会调用SpecialAccount中的Withdraw方法。这里更像是函数的覆盖?

(3)多重继承
Lua中实现多重继承与一般的继承不同,因为有多个基类,一种实现如下:
local Account = { --基类Account
balance = 0,
Withdraw = function(self , v)
self.balance = self.balance - v
end,
Deposit = function(self , v)
self.balance = self.balance + v
end,
}

function Account:New(obj)
obj = obj or {}
setmetatable(obj , Account)
Account.__index = Account
return obj
end

local Name = { --基类Name
GetName = function(self)
return self.name
end,
SetName = function(self , na)
self.name = na
end,
}

function Name:New(obj)
obj = obj or {}
setmetatable(obj , Name)
Name.__index = Name
return obj
end

function Search(key , pList) --用于在基类中搜索某个字段
for k in pairs(pList) do
if pList[k][key] then
return pList[k][key]
end
end
return nil
end
function CreateClass(...) --产生多重派生类
local c = {}
local parent = {...}
setmetatable(c , {__index = function(t,k)--在多重派生类中索引某个字段时到其基类中搜索该字段
return Search(k , parent)
end
})
c.__index = c
function c:New(o)
o = o or {}
setmetatable(o , c)
return o
end
return c
end

local NamedAccount = CreateClass(Account , Name)
local na = NamedAccount:New{name = "Smith"}
print(na:GetName())
NamedAccount为由基类Account和Name派生出来的类,使用CreateClass产生。当调用na:GetName()函数时,首先到na中查找该方法,之后到NamedAccount中查找,再到__index字段查找,发现为函数,则调用该函数,到Account中查找后到Name中查找。由于多重继承在调用基类方法时需要这样查找,性能便下降许多。为了改进性能,可以稍微修改一下:
function CreateClass(...) --产生多重派生类
local c = {}
local parent = {...}
setmetatable(c , {__index = function(t,k)--在多重派生类中索引某个字段时到其基类中搜索该字段
local v = Search(k , parent)
c[k] = v
return v
end
})
c.__index = c
function c:New(o)
o = o or {}
setmetatable(o , c)
return o
end
return c
end
这样,只有该函数调用一次,就会复制到对象中,下次调用便可以直接使用该对象的方法了。但这种改进也只会影响到该派生类对象,不会继续向下沿用。

(4)私密性(封装)
Lua中由于使用table来表示类,因此没有提供任何私密性的机制,但是可以进行模拟,基本思想是用两个表来表示一个类:一个数据表,用于存储类的数据,另一个为方法类,封装类的方法,用户只能使用方法类。如下:
function NewAccount(initbalance)
local self = {balance = initbalance or 0}
function Deposit(v)
self.balance = self.balance + v
end
function Withdraw(v)
self.balance = self.balance - v
end
function GetBalance()
return self.balance
end
return {
Deposit = Deposit,
Withdraw = Withdraw,
GetBalance = GetBalance
}
end

local a1 = NewAccount()
a1.Withdraw(100)
print(a1.GetBalance())
该实现中,定义了一个self表,用于存放数据,也定义了方法,但实际返回的表只是对方法的封装,将用户使用的方法名与实际的方法名对应起来,这些方法都可以直接访问self数据表,因此不需要额外的self参数。当NewAccount返回后,self中的数据就只能通过返回表的几个方法访问,而不能直接访问,这样就实现了数据的私密性。我们甚至可以封装一些私密的方法,只要不将该方法对应到返回的表中即可:
function NewAccount(initbalance)
local self = {balance = initbalance or 0 , LMT = 10000.0}
function Deposit(v)
self.balance = self.balance + v
end
function Withdraw(v)
self.balance = self.balance - v
end
function GetBalance()
return self.balance + Extra()
end
function Extra()
if self.balance >= self.LMT then
return self.balance * 0.1
else
return 0
end
end
return {
Deposit = Deposit,
Withdraw = Withdraw,
GetBalance = GetBalance
}
end

local a1 = NewAccount(100000)
print(a1.GetBalance())
上例中定义了私有方法Extra,但没有对应到返回的表中,因此它是一个私有的方法。

初次发表时存在很多错误,2013-12-17再次修改。


你可能感兴趣的:(面向对象,lua)