面向对象
Lua中的table就是一种对象,这句话可以从3个方面来证实。
首先,table于对象一样可以拥有状态。
其次,table也与对象一样拥有一个独立于其值的标识(一个self)。例如,两个具有相同值的对象是两个不同的对象。
最后,table与对象一样具有独立于创建者和创建地的生命周期。
对象有其自己的操作,table同样也有:
Account = { balancce = 0 }
function Account.withdraw(v)
Account.balance = Account.balance - v
end
上面的代码创建了一个新的函数,并将该函数存入到了Account的withdraw字段中。如需调用可以这样:
Account.withdraw(100.00)
但这种函数中使用的是全局名称“Account”是一个不好的编程习惯。因为这个函数只能针对特定对象工作,并且这个特定对象必须存储在特定的全局变量中。如果改变了全局变量的名称,withdraw就再也不能工作了:
a = Account;
Account = nil
a.withdraw(100.00) --- error
这种行为违反了对象的特性,即对象拥有独立的生命周期。
有一种灵活的方法,即指定一项操作作用的“接受者”,因此需要一个额外的参数来表示该接受者。这个参数通常称为self或this:
function Account.withdraw(self,v)
self.balance = self.balance - v
end
a1 = Accont;
a2 = {balance = 0, withdraw = Account.withdraw}
Account = nil
a1.withdraw(a1,100.00) -- OK
a2.withdraw(a2, 260.00) -- OK
**使用self参数是所有面向对象语言的一个核心。**大多数面向对象语言都能对程序员隐藏部分self 参数,从而使得不必再显示的声明这个参数。
Lua只需要使用冒号,则能隐藏该参数。
function Account:withdraw(v)
self.balance = self.balance - v
end
冒号的作用是在一个方法定义中添加一个额外的隐藏参数,以及在一个方法调用中添加一个额外的实参。
冒号只是一种语法的便利(语法糖),并没有引入什么新的东西。
1.1 类(实例化)
Lua中没有实际的类的概念,每个对象只能自定义行为和形态。不过在Lua可以模拟类。
在Lua中模拟类也不困难,可以参照一些基于原型的语言,例如Self和NewtonScript。在这些语言中对象是没有“类型”的,而是每个对象
在Lua中实现原型很简单,使用继承即可。更准确的说,如果有2个对象a和b,要让b作为a的一个原型,只需要:
setmetatable(a,{__index = b})
在此之后,a就会在b中查找它没有的操作。若将b称为是对象a的类,只不过是术语上的一个变化。
回到先前的银行账户实例。为了创建更多与Account行为类似的账号,可以让这些对象从Account行为中继承这些操作。具体做法就是使用__index元方法。
function Account:new()
local o = o or {}
setmetatable(o,self)
self.__index = self
return o
end
--当调用Account:new时,self就等于Account。因此此处Account来代替self也没问题。不过在引入类继承时,使用self则更为准确。
a = Account:new{balance = 0}
a:deposit(100.00)
1.2 继承
由于类也是对象,它们也可以从其他类中获得方法。这种行为就是一种继承。
Account = {balance = 0}
function Accpunt:new (o)
o = o or {}
setmetatable(o,self)
self.__index = self
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
function Account:withdraw(v)
if self.balance < v then error("球钱没得 还想超?gun") end
self.balance = self.balance - v
end
-----------------------以下是派生类 start
SpecialAccount = Account:new(limit = 100)
--这里创建派生类的时候 并且为派生类声明了一个新的变量 limit 并且赋值
function SpecialAccount:withdraw(v)
if v - self.balance >= self:getLimit() then
error("你贷款最多贷".. self:getLimit())
end
self.balance = self.balance - v
end
function SpecialAccount:getLimit()
return self.limit or 0
end
----------------------- end
----------------------- 创建实例
local user = SpecialAccount:new()
--创建实例user时 SpecialAccount中没有new,查找元表Account,__index字段中的new,此时就是Account:new()
user.deposit(5000.00)
--这里执行时,其实是执行的Account的deposit
user.withdraw(5099.00)
--这里是执行的SpecialAccount的withdraw
以上就是完成一个类的继承。
1.3 多重继承
多重继承的方法在于用一个函数作为__index元字段。例如在一个table的元表中,__index字段为一个函数。那么在原table中找不到一个key字段,就会调用 这个函数。基于这点就可以让__index在其他地方查找这个key。
多重继承意味着一个类可以继承多个基类。因此不能使用一个类中的方法来创建子类,而是要定义一个特殊的函数来创建。在这个函数中会创建一个新的table表示新类,其中一个参数表示新类的所有基类,创建时它会设置元表中的__index元方法,而多重继承正是在这个元方法中实现的。
-- 辅助查找函数 遍历列表来找到对应的Key
local function search(k,plist)
for i = 1 ,#plist do
local v = plist[i][k]
if v then return v end
end
end
fucntion createClass(...)
local c = {}
local parents = {...}
setmetatable(c,{__index = function(t,k)
return search(k,parents)
end})
c.__index = c
function c:new(o)
o = o or {}
setmetatable(o,c)
return o
end
return c -- 返回新类
end
---------------------------------以下来实现多继承
Named = {}
function Named:getName()
return self.name
end
function Named:setname(n)
self.name = n
end
NameAccount = createClass(Account,Named)
account = NameAccount:new({name = "Paul" })
---这里调用new的时候 会先从NameAccount这里面去找然后,查找它的元表c的__index,发现没有,然后再去找c的元表的__index是一个方法,方法中调用了search,发现Account中有一个new,就实现了初始化。
print(account.getName()) --- Paul
--这里调用 同上
以上就是一个多重继承的实现方式了。但是这种方式性能不如单一继承,由此做了一个改进:
setmetatable(c,{__index = function(t,k)
local v = search(k,parents)
t[k] = v ---这里把它保存下来 以便下次访问
return v
end})
1.4 私密性
Lua在设计对象时,没有提供私密性机制。不过可以实现:
通过2个table来表示一个对象。一个table用来保存对象的状态;另一个用与对象的操作。对象本身是通过第二个table来访问的(这里可以理解为C#的属性,不过get是一个单独的表)。
function newAccount (initBalance)
local self = {balance = initBalance} --这里使用了闭包方式 将balance 的访问权限 固定在了 newAccount的table self中 因为外部访问不到 self
local withdraw = function (v)
self.balance = self.balance - v
end
local deposit = function (v)
self.balance = self.balance + v
end
local getBalance = function () return self.balance end
return {
withdraw = withdraw,
deposit = deposit,
getBalance = getBalance
}
end
这种设计给予存储在self table中所有的东西完全私密性。当newAccount返回后,就无法直接访问这个table了。只能通过newAccount创建的函数来访问它。