Lua 基础之面向对象编程

来源:互联网 发布:淘宝图防盗图怎么设置 编辑:程序博客网 时间:2024/05/18 18:42

对象

lua 中的 table 其实就是对象,理由如下
1. table 与对象一样可以拥有状态
2. table 与对象一样拥有一个独立于其值的标识 self,值相同的两个 table 是两个不同的对象
3. table 与对象一样拥有独立于创建者和创建地的生命周期

方法

table 中字段的值可以是任意类型,如果某个字段的值是一个函数,那这个函数就称为对象的方法

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

withdraw 是全局对象 Account 的一个方法。对象具有独立的生命周期,因此对象的方法也只有这个对象本身能访问,比如下面的操作是错误的

a = AccountAccount = nila.withdraw(10)

在上面的代码,我们将对象 Account 改名为 a,然后通过 a 去调用 withdraw 方法,执行的时候会发现 Account 对象不存在,当然 Account.balance 也会找不到

self 参数

对象中的方法要访问对象的其它字段,需要显式地在方法中使用全局对象名,这种编程习惯是非常不好的,这种方式无法从一个对象拓展出其它对象,更无法实现类的概念,因为只能使用创建对象时唯一的全局对象名,比如上面的 Account,任何操作都只能使用 Account 这个名称
这个问题的解决方案是给每一个方法指定其操作的”接收者“,通常使用 self 或 this 来表示这个”接收者“,在 lua 中使用 self 参数

Account = {balance = 100}Account.withdraw = function(self, v)    self.balance = self.balance - venda = AccountAccount = nila.withdraw(a, 1)print(a.balance)

方法定义时指定第一个参数为 self,然后在调用方法时把具体的对象传进去。一种更简单写法是使用冒号,使用这种方式可以隐藏 self 参数

function Account:withdraw(v)    self.balance = self.balance - venda:withdraw(1)

实际上 lua 并没有类的概念,但我们可以使用”原型“来实现类与对象之间的关系。把一个表设为另一个表的原型(metatable),那这个原型表就相当于是一个类,而继承自这原型表的表就是对象
lua 中的类就是对象元表来实现的,通过设置元表的 __index 元方法,当访问一个对象不存在的字段或方法时,可以通过 __index 元方法在类中查找

-- 定义类Account = {balance = 0}-- 构造函数function Account:new(o)    o = o or {}    setmetatable(o, self)    self.__index = self    return oendfunction Account:withdraw(v)    self.balance = self.balance + vendfunction Account:deposit(v)    self.balance = self.balance - vend-- 创建对象a = Account:new()a:withdraw(100)a:deposit(50)print(a.balance)

分析上面代码的执行过程:
* Account:new() 会创建一个 table(对象),这个 table 的 metatable 是 Account
* a 没有 withdraw 方法,调用 a:withdraw(100) 时会到 Account 中查找 withdraw 方法,找到之后执行 self.balance = self.balance + v,此时 self 的值是 a,a 没有 balance 字段,因此同样的会到 Account 中查找;赋完值之后 a 就已经从 Account 继承了 balance 字段,下次访问就不需要到 Account 中查找了
* 调用 a:deposit(50) 同样会到 Account 中查找,因为 a 并没有 deposit 方法;但 a 已经有 balance 字段了,因为 a.balance = a.balance - v 可直接执行

继承

其实实现类的过程就是一种继承的行为,一个对象继承自另一个对象,被继承的对象就可以看作是一个类。我们可以继承一个类后重写它的一些方法生成一个子类,再继承这个子类来创建对象。执行对象的方法时会自下往上查找这个方法,如果在本对象找不到方法则向它的类原型查找,如果还没找到,则向父类查找,一直找到最原始的父类。

-- 定义父类Account = {balance = 0}function Account:new(o)    o = o or {}    setmetatable(o, self)    self.__index = self    return oendfunction Account:withdraw(v)    if self.balance < v then        error("你的钱不够了!", 2)    else        self.balance = self.balance - v    endendfunction Account:deposit(v)    self.balance = self.balance + vend-- 继承SpecialAccount = Account:new()-- 重写方法function SpecialAccount:withdraw(v)    if self.balance < v then        if self:getLimit() < v - self.balance then            error("你不能透支这么多钱!", 2)        else            self.balance = self.balance - v        end    else        self.balance = self.balance - v    endend-- 子类的新方法function SpecialAccount:getLimit()    return self.limit or 0end-- 创建子类对象a = SpecialAccount:new({limit = 100})a:withdraw(90)-- a:withdraw(20)  -- errorprint(a.balance)

多重继承

lua 中的继承不是原生,实现的关键是元表的 __index 元方法,单一继承的方式是设置 __index 为元表本身,这样在子类找不到的字段和方法就会到父类,也就是子类的元表去找。如果 __index 不设置元表本身,而是一个函数,通过这个函数遍历它子类的父类,再找到对应的字段或方法,这样就可以实现多继承了
可以使用工厂模式来动态创建新类

-- 新类工厂CreateClass = function(...)    -- 定义新类    local class = {}    -- 定义父类集合    local parent = {...}    -- 设置新类的元表    setmetatable(        class,        {__index = function(t, k)                for _, v in ipairs(parent) do                    if v[k] then                        return v[k]                    end                end            end}    )    -- 新类的元方法为自身    class.__index = class    -- 定义新类构造函数    class.ctor = function(self, o)        o = o or {}        -- 设置对象的元表为新类        setmetatable(o, class)        return o    end    return classend-- 使用工厂生产新类NamedAccount = CreateClass(Account, Name)-- 创建新类实例ac = NamedAccount:ctor()ac:set_name("myAccount")print(ac:get_name())ac:withdraw(100)print(ac:get_balance())
  • 在工厂方法里面,创建一个新类,然后指定新类的元表,元表的 __index 值不是具体的某个父类,而是一个新方法,准确来说是一个闭包,还包括非局部变量 parent 通过这个方法可以遍历所有的父类,找到要找的字段或方法。
  • 这样一个拥有多个父类的新类就定义出来了,再给这个新类定义一个构造方法,设置其实例的元表为这个新类,而这个元表的 __index 值也是这个新类本身,这是单一继承的东西
  • 调用这个工厂方法,把所有父类通过参数传进来,就生成了一个拥有多个父类的新类
  • 调用新类的构造方法,实例化新类的一个对象,这个对象的元表是这个新类,而新类的元表是自定义的,其 __index 元方法可以遍历所有父类
  • 在使用的时候,首先通过实例对象去调用某个方法,比如 set_name 方法,发现这个对象没有这个方法,然后就通过它的元表向它的父类查找。它的元表是 NamedAccount,NamedAccount 的 __index 是 NamedAccount 本身,所以解释器会查找 NamedAccount,发现也没有找到 set_name 这个方法,于是继续通过元表向父类查找。NamedAccount 元表的 __index 元方法遍历 NamedAccount 所有的父类,发现 Named 类中有 set_name 这个方法,于是查找结束,调用Name:set_name(v)

单一方法

使用一个方法来表示一个对象,这种方式其实是用闭包 colsure 来表示对象。单一方法实现的对象虽然无法继承,但具有完全的私密性,比如实现一个调度器

newObject = function(value)    return function(action, v)        if action == "get" then            return value        elseif action == "set" then            value = v        else            error("invalid action")        end    endendd = newObject(10)print(d("get"))d("set", 100)print(d("get"))

私密性

lua 本身没有提供私密性的机制,但我们可以利用 lua 的灵活性来模拟类的私密性。我们在创建对象的时候不返回对象本身,而是返回所有外部接口的集合,通过这些外部接口就可以访问和操作这个对象。

Account = function(balance)    local self = {_balance = balance}    local _withdraw = function(v)        self._balance = self._balance - v    end    local _deposit = function(v)        self._balance = self._balance + v    end    local _get_balance = function()        return self._balance    end    return {withdraw = _withdraw, deposit = _deposit, get_balance = _get_balance}enda = Account(0)a.withdraw(100)print(a.get_balance())a.deposit(10)print(a.get_balance())

总结

lua 中类的概念主要通过继承来实现的,实现继承主要有以下两步
1. 设置元表 setmetatable(child.parent)
2. 设置 __index 元方法 parent.__index=parent

原创粉丝点击