• Lua中,table就是对象
    • table和对象的共同点:
      1. 拥有状态
      2. 拥有一个与其值无关的标识(self)
      3. 两个具有相同值的对象(table)是两个不同的对象
      4. 一个对象可以具有多个不同的值
      5. 有与创建者和被创建者位置无关的生命周期
      6. 都可以有自己的操作(方法)

方法(method)

方法的错误使用

  1. Account = {balance = 0}
  2. function Account.withdraw(v)
  3. Account.balance = Account.balance - v
  4. end
  5. Account.withdraw(100) --运行成功
  6. a, Account = Account, nil
  7. 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)

  • 使用一个额外的参数表示接收者,称为selfthis

    • 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
--]]

类的改进

  1. 不创建新table作为元表,而是直接将Account(类)作为元表
  2. 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
        return v
        
        end end end

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表示一个对象

      1. 一个table用来保存对象状态
      2. 另一个table用于保存对象的操作
      3. 通过第二个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中,所以也可以直接访问 ```