- Lua中,table就是对象
- table和对象的共同点:
- 拥有状态
- 拥有一个与其值无关的标识(self)
- 两个具有相同值的对象(table)是两个不同的对象
- 一个对象可以具有多个不同的值
- 有与创建者和被创建者位置无关的生命周期
- 都可以有自己的操作(方法)
- table和对象的共同点:
方法(method)
方法的错误使用
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance - v
end
Account.withdraw(100) --运行成功
a, Account = Account, nil
a.withdraw(100) --运行失败
函数中使用全局名称Account,是一个错误的方式
- 该函数只能针对特定对象工作
- 即使针对特定对象,这个函数也只有在特定的全局变量中时才能工作
- 解释:在上诉代码中进行解释,从第9行代码开始。因为清空了Account中的内容,所以此时全局变量Account对应的是nil。此后函数.withdraw()中的Account.balance也变成了nil,因为在这个函数中,Account代表的是全局变量中的Account(也就是第1行代码中的),但是该Account已经为nil。就算给a赋了清空Account之前的指针地址,函数.withdraw()中的Account.balance也还是为空,应为全局变量Account是nil。
- 使用该方式写方法,方法中的全局变量Account是静态的,它绑定在了Account中,并不是动态的。
正确的使用方法(使用self)
使用一个额外的参数来表示接收者,称为self或this
- self指代调用该方法的类(谁调用self就指谁)
- 使用参数self是面对对象的核心点。还可以使用冒号操作符(:)隐藏该参数
- 冒号作用:在方法调用中增加一个额外的实参,或在方法的定义中增加一个额外的隐藏参数
- 添加的参数就是冒号前的参数 ```lua Account = {balance = 0}
- 冒号作用:在方法调用中增加一个额外的实参,或在方法的定义中增加一个额外的隐藏参数
function Account.withdraw(self, v) self.balance = self.balance - v end
a, Account = Account, nil a.withdraw(a, 100)
Account = { balance = 0, widthdraw = function(self, v) self.balance = self.balance - v end }
function Account:deposit(v) —使用 冒号 来省略参数self self.balance = self.balance + v end
Account.deposit(Account, 200) —可以使用 点分 语法调用 Account:widthdraw(100) —可以使用 冒号 调用
---
<a name="wIaUw"></a>
# 类(Class)
- **类**在**对象的创建中**扮演了**模板**(mold)的作用。**对象是特定类的实例**。
- **实例化之后,就可以使用所有父类的方法**
- **Lua**可以参考基于原型的语言,来**模拟类**:
- 对象不属于类,对象是类的实例
- 每个对象可以有一个原型。
- 原型也是一种普通的对象,对象遇到未知操作时会先在原型中查找。
- **表示一个类,只需要创建一个专门用作其他对象的原型对象即可**
<a name="cl1ff"></a>
### 实现原型
- 使用**继承**的思想来**实现原型**:
- **setmetatable(A, {__index = B})**
- A会在B中查找A中没有的操作(B为对象A的类 或 A是对象B的实例)
```lua
Account = { --Account作为类
balance = 0,
deposit = function(self, v)
self.balance = self.balance + v
end
}
local mt = { --作为原型,查询不存在的索引时,到Account上查找索引
__index = Account
}
function Account.new(o) --用来实例化对象
o = o or {}
setmetatable(o, mt) --给table设置原型元表
return o
end
a = Account.new() --实例化Account生成对象a(将元表设置给a)
a:deposit(10) --因为a中原本没deposit函数,因此会在原型中进行查找
print(a.balance) --输出:10
--[[
在实例化Account类后,对象a中是没有balance的,如果此时访问a.balance,会通过元表查找,
得到0;
在首次访问a:deposit时,对象a中也没有deposit函数,因此在元表中查找,找到之后运行该方法,
运行之后,会让a.balance有意义,因此此时访问a.balance就会直接得到对象a中的balance
--]]
类的改进
- 不创建新table作为元表,而是直接将Account(类)作为元表
- new方法也是用冒号语法 ```lua function Account:new(o) —实例化一个对象(给一个原型) o = o or {} self.__index = self —将self中的函数加入元方法 setmetatable(o, self) —无论何时,self都代表调用(类)的对象 return o end
a = Account:new() —调用时候也要用冒号,因为new中的self是Account(类)自己
---
<a name="p8YHF"></a>
# 继承(Inheritance)
- **继承**:**类**也是对象,**可以从其他类中获得方法**
- **类**可以**派生**一个**子类**(**实例化**一个对象)
- 该**子类继承**了**父类**的new**方法**,因此**子类还可以实例化一个子类**2
- 但是,因为子类中不存在new方法,所以new方法来自父类,但是此时调用new的是子类子类,因此此时new中的self代表的是子类的self。
- 还**可以在子类中**进行方法的**添加和重写**
- **添加和重写后**,**子类和子类的实例**就**可以调用**更新之后的方法
- 即:每个table都有自己的元表;在table中访问不存在的索引时,就会在元表中查找;元表同样有自己的元表,元表中不存在时,就在元表的元表中查找
```lua
Account = {balance = 0} --Account类
function Account:new(o) --实例化方法
o = o or {}
self.__index = self --将调用该方法的 类的本身 设为元方法
setmetatable(o, self)
return o
end
function Account:withdraw(v) --方法
if v > self.balance then
error("insufficient funds1")
end
self.balance = self.balance - v
end
SpecialAccount = Account:new() --对象SpecialAccount继承父类Account(实例化一个子类)
s = SpecialAccount:new {limit = 1000.00} --继承后实例化一个子类s
--[[
SpecialAccount函数继承了new,但是在执行new时,self指向的是调用new方法的对象,也就是
SpecialAccount。
s的原表和__index是SpecialAccount,SpecialAccount又继承自Account。
如果执行s:deposit(1),会先在s中寻找,然后再SpecialAccount中寻找,最后在Account中寻找
--]]
function SpecialAccount:withdraw(v) --重写父类的withdraw方法
if v - self.balance >= self:getLimit() then
error("insufficient funds2")
end
self.balance = self.balance - v
end
function SpecialAccount:getLimit() --新添getLimit方法
return self.limit or 0
end
s:withdraw(10000) --调用对象s的withdraw方法
SpecialAccount:withdraw(100) --同样会调用SpecialAccount中的withdraw方法
--输出:insufficient funds2
多重继承(Multiple Inheritance)
- 多重继承:一个类能够有多个父类
- 实现原理:元表的__index字段为一个函数,使用这个函数搜索所有父类
- 不能使用其中一个父类来创建子类,应该定义一个独立的函数来创建子类。
- 该方法下,虽然实现了多重继承,但是 类和其多个父类 与 类和其实例 的关系是不同的
- 类和其多个父类:每个实例属于单个类,只是进行了查找
- 类和其实例:类是其实例的元表
```lua
function Search(k, parent) —在多个父类中查找方法
for i = 1, #parent do
local v = parent[i][k] —判断该方法是否存在
if v then
end end endreturn v
function createClass(…) —创建有多重继承的类 local c = {} local parent = {…}
setmetatable( --给c一个元表,该元表中的__index元方法设置成函数,
c, --而不是一个table,在调用时,使用该函数查找所有父类
{
__index = function(t, k)
return Search(k, parent)
end
}
)
function c:new(o) --创建一个实例
o = o or {}
self.__index = self --将类作为实例的元表
setmetatable(o, self)
return o
end
return c
end
Named = { —Named类 getname = function(self) print(self) return self.name end, setname = function(self, name) self.name = name end } Account = { —Account类 name = “yxf” }
local NamedAccount = createClass(Named, Account) —创建一个继承多个父类的类NameAccount local account = NamedAccount:new({}) —实例化NameAccount类 print(account:getname()) —输出:yxf
- **运行过程**:
1. Lua在**account**中**找不到getname**字段
1. **查找**account的**__index字段**,即元表NamedAccount
1. NamedAccount中**也没有**getname字段
1. **查找__index字段**,使用**函数**进行**搜索**
1. **先在Account**中搜索,没找到;然**后在Named中**搜索,找到,**返回结果**;
<a name="goFSu"></a>
#### 增加性能的方法(有缺点)
- 多继承的性能不如单继承,将被继承的方法复制到子类中;再次访问时,就会和访问局部变量一样快
- 缺点:不会沿着继承层次向下传播
- 因为每次保存都保存在了子类中,别的类继承时,就要重新储存
```lua
--修改createClass函数中的设置元表
setmetatable( --给c一个元表,该元表中的__index元方法设置成函数,
c, --而不是一个table,在调用时,使用该函数查找所有父类
{
__index = function(t, k)
local v = Search(k, parent)
t[k] = v --将方法保存到,初次调用该方法的实例中
return v
end
}
)
私有性(Privacy)
- 私有性:信息隐藏,私有变量只能由对象自己控制,不能直接访问
Lua中没有直接提供私有性的机制,但是能够通过模拟来实现:
使用2个table来表示一个对象:
- 一个table用来保存对象状态
- 另一个table用于保存对象的操作
- 通过第二个table来访问第一个table(也就是访问对象状态)
- 注:表示对象状态的table(第一个table)不保存在其他table的字段中,而是保存在闭包中 ```lua function newAccount(initialBalance) —创建含有私有性的类 local self = { —私有变量,使其成为闭包 initialBalance = initialBalance, LIM = 10000.00 }
local extra = function() —私有函数,使其成为闭包 if self.initialBalance > self.LIM then
return self.initialBalance * 0.10
else
return 0
end end
local withdraw = function(v) —访问私有变量的方法 self.initialBalance = self.initialBalance - v end
local getBalance = function() —访问私有变量的方法 return self.initialBalance + extra() end
return { —返回接口table,使私有变量成为闭包 withdraw = withdraw, getBalance = getBalance } end
local account1 = newAccount(100) —创建私有性的类 print(account1.getBalance()) —使用.来对私有方法进行访问
- **有了私有性**之后,**不需要额外**的self**参数**,应为self**参数被用作闭包**,并且**只能使用点分方法**访问
- 因为**self内容是完全私有**的,所以**不能直接访问**,只能**通过创建闭包时**定义的**私有方法**进行**访问**
<a name="S4IA9"></a>
### 单方法对象(Signle-method Object)
- 上诉实现的一种特例,即**只有一个方法的情况**
- 不用创建接口table,**直接将方法返回**
- 可以**用于根据不同**的**参数完成不同任务**的**分发方法**(dispatch method)
- 这种方式**高效**,每个对象使用一个闭包没**开销更低**;并且访问单方法对象中某个成员只能通过该对象的唯一方法进行,是**完全私有**的
```lua
function newPrivateObject(value)
local balance = value --私有变量
return function(action, v) --创建闭包函数,为分发方法
if action == "get" then
return balance
elseif action == "set" then
balance = v
else
error("invalid action")
end
end
end
d = newPrivateObject(0) --创建对象
print(d("get")) --使用其get方法,输出:0
d("set", 20) --使用其set方法,进行设置
print(d("get")) --输出:20
------------------------------------------------------
--下方代码将单方法对象加入Class中使用
local Class = {}
function Class:new(o)
local o = o or {}
o.d = newPrivateObject(0) --将私有变量加入类中
self.__index = self
setmetatable(o, self)
return o
end
local o1 = Class:new() --对象o1
local o2 = Class:new() --对象o2
o1.d("set", 10) --设置o1的私有变量
print(o1.d("get")) --输出:10
print(o2.d("get")) --输出:0
对偶表示(Dual Representation)
- 实现私有性的一种方法,把table当作键,同时又把对象本身当作这个table的键
- 即用一个table储存私有变量,储存时,使用对象本身来作为键
- 使用balance[self]比使用self.balance慢,因为前者对于对象来说是外部变量,而后者是局部变量 ```lua local balance = {} —使用一个table保存,所有有对象中的私有变量balance
Account = {}
function Account:new(o) o = o or {} self.__index = self setmetatable(o, self) balance[o] = 0 —初始化balance,将对象本身作为键,储存在tabl balance中 return o end
function Account:getBalance() return balance[self] end
function Account:setBalance(v) balance[self] = v end
local acc1 = Account:new()
acc1:setBalance(10)
print(acc1:getBalance()) —输出:10
—print(balance[acc1]) 因为私有变量在该table balance中,所以也可以直接访问
```