快速掌握Lua 5.3 —— 面向对象编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/VermillionTear/article/details/50650883

Q:如何定义对象的方法以及调用对象的方法?

A:面向对象的特殊性在于它以this指针的方式传递了对象本身,并且这种操作是隐藏起来的。
在Lua中使用:实现面向对象方式的调用。:只是一个语法糖,它同时在方法的声明与实现中增加一个名为self的隐藏参数(对象本身)。

Account = {balance = 1000}    -- 账户余额初始为1000。
--[[ 取钱。
     使用面向对象的方式隐藏了"self"参数,
     "withdraw()"完整的参数列表是"Account.withdraw(self, v)"。]]
function Account:withdraw(v)
    self.balance = self.balance - v
end

--[[ 使用面向对象的方式隐藏了"self"参数,
     实际传递给"withdraw()"的参数是"Account"和"100.00"。]]
Account:withdraw(100.00)
print(Account.balance)    --> 900.0

我们可以用.定义函数,而用:调用函数,或者反之亦然,只要我们正确的使用这个被隐藏的self参数。

Account = {balance = 1000}    -- 账户余额初始为1000。
function Account.withdraw(self, v)    -- 使用"."定义函数。
    self.balance = self.balance - v
end
Account:withdraw(100.00)    -- 使用":"调用函数。
print(Account.balance)    --> 900.0

-- 存钱。
function Account:deposit(v)    -- 使用":"定义函数。
  self.balance = self.balance + v
end
Account.deposit(Account, 600.00)    -- 使用"."调用函数。
print(Account.balance)    --> 1500.0

Q:如何实现类?

A:类在面向对象语言中就好象一个模板,通过模板所创建的实例就具有模板中规定的特性。Lua中没有类的概念,每一个对象规定自己的行为,每一个对象就是自己的实例。不过在Lua中模拟“类”并不难,我们可以用继承的概念,使用两个对象,让其中一个对象作为另一个对象的“类”,
setmetatable(a, {__index = b}) -- b作为a的类,在a中找不到的方法都将去b中寻找。
继续扩展银行账户的例子,

Account = {balance = 0}    -- 账户余额初始为0。
function Account:new (o)
  o = o or {}   -- create object if user does not provide one
  setmetatable(o, self)    -- 新对象的"metatable"是"Account"(或其子类)。
  self.__index = self    -- 新对象"metatable"中的"__index"依旧是"Account"(或其子类)。
  return o
end

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

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

--[[ "account1"继承了"Account"中的特性,
     就好象"account1"是由"Account类"创建出来的一样。
     这一句代码后,设置了"account1"的"metatable"是"Account",
     "Account.__index"依旧是"Account"。]]
account1 = Account:new()
-- 这里打印的是"Account.balance","Account.balance"作为"Account"实例的默认值。
print(account1.balance)    --> 0
print(Account.balance)    --> 0
--[[ "account1:deposit(500.00)",实际上调用的是"account1.deposit(a, 500.00)",
     "account1"中没有"deposit()",所以去找"account1"的"metatable"中的"__index",
     即"getmetatable(account1).__index",即"Account"。
     又因为"Account"是一个"table",所以"account1.deposit(a, 500.00)"
     相当于"Account.deposit(a, 500.00)"。]]
account1:deposit(500.00)
--[[ 这里打印的是"account1.balance",
     因为在"Account.deposit()"中为"self.balance"赋值的同时定义了"account1.balance"。]]
print(account1.balance)    --> 500
print(Account.balance)    --> 0

Q:如何实现继承?

A:上面的例子中已经初步展现了继承的方式,下面再使用一个更贴切的例子进行说明,

Account = {balance = 0}

function Account: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 v > self.balance then error"insufficient funds" end
    self.balance = self.balance - v
end

--[[ 信用卡账户继承自普通账户,可以透支。
     "CreditAccount"是"Account"的一个“实例”,
     但他同时也可以作为一个“类”以产生其他实例。]]
CreditAccount = Account:new{limit = 1000.00}

-- 为了让信用卡账户能够透支,需要重写"withdraw()"方法。
function CreditAccount:withdraw (v)
    -- 信用卡账户在一定额度内可以透支。
    if v - self.balance >= self:getLimit() then
        error"insufficient funds"
    end
    self.balance = self.balance - v
end

function CreditAccount:getLimit ()
    return self.limit or 0
end

--[[ 新的实例也可以规定自己的限额。
     "CreditAccount"中没有"new()",实际调用的是"Account.new()"。]]
creditaccount1 = CreditAccount:new{limit = 2000.00}
--[[ "creditaccount1"中没有"deposit()",
     "CreditAccount"中没有"deposit()",实际调用的是"Account.deposit()"。]]
creditaccount1:deposit(100.00)
-- 此时调用的是"CreditAccount:withdraw()"。
creditaccount1:withdraw(200.00)
print(creditaccount1.balance)    --> -100.0

Q:如何实现多重继承?

A:将__index赋值为一个函数,函数中可以搜索想要找的表(“父类”)。
还记得吗,当__index是个函数时,Lua调用它,以”table”和缺失的”key”作为参数。而当__index是一个”table”时,Lua直接以缺失的”key”作为它的”key”再次访问他(相当于拿着缺失的”key”在它这张”table”中寻找)。

------------------- "Account类" -------------------
Account = {balance = 0}

function Account: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 v > self.balance then error"insufficient funds" end
    self.balance = self.balance - v
end
--------------------------------------------------

-- look up for 'k' in list of tables 'plist'
local function search(k, plist)
    for i = 1, #plist do
        local v = plist[i][k]    -- try 'i'-th superclass
        if v then return v end
    end
end

function createClass(...)
    local c = {}        -- new class

    for i = 1, select('#', ...) do
        -- arg = {[1] = Account, [2] = Named}。
        arg[i] = select(i, ...)
    end

    --[[ class will search for each method in the list of its
        parents ('arg' is the list of parents)]]
    setmetatable(c, {__index = function (t, k)
        return search(k, arg)
    end})

    -- prepare 'c' to be the metatable of its instances
    c.__index = c

    -- define a new constructor for this new class
    function c:new (o)
        o = o or {}
        setmetatable(o, c)
        return o
    end

    -- return new class
    return c
end 

Named = {}    -- 姓名类,记录账户的姓名。
function Named:getname ()
    return self.name
end

function Named:setname (n)
    self.name = n
end

-- 创建一个新类"NamedAccount",其继承于"Account"与"Named"。
NamedAccount = createClass(Account, Named)
account = NamedAccount:new{name = "Paul"}
--[[ "account"中没有"getname",
     所以找"account""metatable.__index""metatable""NamedAccount""NamedAccount""__index"还是"NamedAccount"。
     而"NamedAccount"中依旧没有"getname()",
     所以找"NamedAccount""metatable.__index",是个函数,
     所以Lua调用这个函数,并将"NamedAccount""getname"作为参数传入。
     函数中又调用"search()""search()"中首先在"Account"中找"getname",
     没找到,又在"Named"中找"getname",找到了,所以调用"getname()"。]]
print(account:getname())     --> Paul

多重继承的效率可能比单一继承的效率低很多(因为多了search()这个过程)。如果想提高效率的话可以将找到的父类的函数拷贝到子类中,

setmetatable(c, {__index = function (t, k)
    local v = search(k, arg)
    t[k] = v    -- "Named.getname()"存储在了"account"中。
    return v
end})

这样,除了第一次调用,之后的调用实际上是调用子类中拷贝过来的父类的函数,省去了search()的过程,效率就高很多。但是这种做法也有缺陷,在程序运行起来之后,就很难改变父类中的方法了,因为即使更改了,子类也不会继承过去(子类保存了父类原先的方法)。

Q:如何定义私有成员或私有方法?

A:使用两个”table”,其一存储私有成员,另一个存储公有成员和公有方法,两个”table”组成”Closure”,私有”table”作为公有”table”的”Closure”被访问,私有方法直接存储在”Closure”中,

function newAccount (initialBalance)
    -- 私有"table"。
    local self = {
        balance = initialBalance,    -- 余额。
        count = 0    -- 积分。
    }

    -- 私有方法,未导出到公有"table"中,外部无法访问。
    local addCount = function (v)
        self.count = self.count + v * 0.1    -- 消费的10%作为积分。
    end

    local withdraw = function (v)
        self.balance = self.balance - v
        addCount(v)
    end

    local deposit = function (v)
        self.balance = self.balance + v
    end

    local getBalance = function () return self.balance end

    local getCount = function () return self.count end

    -- 公有"table"。
    return {
        withdraw = withdraw,
        deposit = deposit,
        getBalance = getBalance, 
        getCount = getCount
    }
end

account = newAccount(100)
print(account.balance)    --> nil
print(account.count)    --> nil
print(account.getBalance(), account.getCount())    --> 100 0
account.deposit(500)
print(account.getBalance(), account.getCount())    --> 600 0
account.withdraw(100)
print(account.getBalance(), account.getCount())    --> 500 10

附加:

1、Lua中的”table”与面向对象编程中的对象有很多相似的地方。像对象一样,”table”有成员变量;像对象一样,”table”本身与其存储的值相互独立,特别在于两个不同的对象拥有相同的值,但他们依旧是不同的对象。反之,一个对象在不同的时间可以有不同的值,但对象本身不会改变;像对象一样,”table”的生命周期与其被谁创建以及在哪儿被创建无关。
2、Lua中的面向对象编程有意思的地方在于,当你需要增加新的方法时,既可以在“类”中添加,也可以在“实例”中直接添加。

-- 以添加一个“增加积分”的方法为例。
-- 在“类”中添加,适用于许多用户均需要该方法。
function CreditAccount:addCount(count) ... end
-- 在“实例”中直接添加,适用于少数用户需要该方法。
creditaccount1 = CreditAccount:new()
creditaccount1.addCount = function(self, count) ... end

3、Lua使用”table”表示对象,本身不提供私有性机制。Lua的设计初衷是针对于中小型程序,通常他们是一个大型系统的一部分。因此Lua避免设计上的冗余以及过多的人为限制,如果对象中的某些成员你不该访问,那么你最好就”just do not do it”。Lua提供给程序员“meta-mechanisms”,程序员可以通过他们模拟许多需要的特性。

猜你喜欢

转载自blog.csdn.net/VermillionTear/article/details/50650883