Lua 基础之面向对象编程

对象

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 = Account
Account = nil
a.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 - v
end

a = Account
Account = nil
a.withdraw(a, 1)
print(a.balance)

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

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

a:withdraw(1)

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

-- 定义类
Account = {balance = 0}
-- 构造函数
function Account:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function Account:withdraw(v)
    self.balance = self.balance + v
end

function Account:deposit(v)
    self.balance = self.balance - v
end

-- 创建对象
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 o
end

function Account:withdraw(v)
    if self.balance < v then
        error("你的钱不够了!", 2)
    else
        self.balance = self.balance - v
    end
end

function Account:deposit(v)
    self.balance = self.balance + v
end

-- 继承
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
    end
end
-- 子类的新方法
function SpecialAccount:getLimit()
    return self.limit or 0
end

-- 创建子类对象
a = SpecialAccount:new({limit = 100})
a:withdraw(90)
-- a:withdraw(20)  -- error
print(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 class
end

-- 使用工厂生产新类
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
    end
end

d = 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}
end

a = 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值