• Lua面向对象编程


    Lua面向对象编程

    来源: http://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”,程序员可以通过他们模拟许多需要的特性。

  • 相关阅读:
    Linux下安装nginx
    MySQL基础
    win10 安装MySQL 5.7.27
    Java IO之File
    java并发编程之ThreadLocal
    原生JS 的60秒倒计时!
    vueX 配合路由导航配置动态路由
    JS获取 当前时间是本年的第几天? 第几周?
    vue书写echarts 100px大小问题
    VUE 父组件与子组件双向数据绑定的方法 做弹框绑定列表页数据的举例
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/7772252.html
Copyright © 2020-2023  润新知