Lua学习笔记 第十六章 面向对象编程

Lua中的table就是一种对象。首先,table与对象一样可以拥有状态;其次,table与对象一样拥有一个

独立于其值的标识(一个self)。最后,table与对象一样具有独立于创建者的生命周期。

在函数中使用全局名称是一个不好的编程习惯。因为这个函数只能针对特定对象工作,并且这个特定

对象还必须存储在特定的全局变量中。这种行为违反了对象特性,即对象拥有独立的声明周期。因此

需要一个额外的参数来指定一项操作所作用的接受者。这个参数通常称为selfthis。当调用这个方法时,

必须指定其作用的对象。

Account = {balance = 0}

function Account.withdraw(self, v)

    self.balance= self.balance - v

end

a1 = Account; Account = nil

a1.withdraw(a1, 100.00)

Lua中使用冒号可以隐藏self参数。

function Account:withdraw(v)

    self.balance= self.balance - v

end

调用时可写为 a1:withdraw(100.00)

冒号的作用是在一个方法定义中添加一个额外的隐藏参数,以及在一个方法调用中添加一个额外的实参。

冒号只是一种语法便利,并没有引入任何新的东西。使用时只要能正确地处理那个额外参数即可。

Account = { balance = 0,

            withdraw= function(self, v)

                self.balance = self.balance - v

             end

      }

function Account:deposit(v)

    self.balance= self.balance + v

end

Account.deposit(Account, 200.00)

Account:withdraw(100.00)

现在对象已有一个标识、一个状态和状态之上的操作。不过还缺乏一个类(class)系统、继承和私密性(privacy)

 

16.1

一个类就是一个创建对象的模具。Lua中没有类的概念,但是可以通过原型来模拟类。类和原型都是一种组织

对象间共享行为的方式。

Lua中可以通过13.4.1节中所描述的继承实现原型。准确地说,如果有两个对象ab,要让b作为a的一个原型,

只需要输入如下语句:

setmetatable(a, {__index = b})

在此之后,a就会在b中查找所有它没有的操作。可以称b为时对象a的类。

之前银行账号的示例。为了创建更多与Account行为类似的账号,可以让这些新对象从Account行为中继承这些操作。

function Account:new(o)

    o = o or {}     -- 如果用户没有提供table,则创建一个

    setmetatable(o,self)     -- 使用Account table自身作为元表

    self.__index= self

    return 0

end

在这段代码之后,创建一个新账户或调用一个方法

a = Account:new{balance = 0}

a:deposit(100.00)

最终的调用情况为:

getmetatable(a).__index.deposit(a,100.00)

a的元表是AccountAccount.__index也是Account。因此,上述表达式可以简化为

Account.deposit(a, 100.00)

新账户aAccount继承了deposit函数,同样它还能从Account继承所有的字段。

 

16.2 继承

假设有一个基类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

若想从这个类派生出一个子类SpecialAccount,以使客户能够透支,则先需要创建一个空类,

从基类继承所有的操作:

SpecialAccount = Account:new()

现在SpecialAccount可以重定义那些从基类继承的方法:

s = SpecialAccount:new{limit=1000.00}

function SpecialAccount:withdraw(v)

    if v -self.balance > self:getLimit() then

        error "insufficient funds"

    end

    self.balance= self.balance - v

end

function SpecialAccount:getLimit()

    returnself.limit  or 0

end

现在调用s:withdraw(200.00)时,Lua就不会在Account中查找了。因为Lua会在SpecialAccount中先找到withdraw方法。

如果只有一个对象需要某种特殊的行为,那么可以直接在该对象中实现这个行为。

 

16.3 多重继承

Lua中使用__index元方法实现面向对象编程是一种集建议、性能和灵活性于一体的做法;

如果要实现多重继承,关键在于用一个函数作为__index元字段。多重继承意味着一个类可以具有多个基类。

因此无法使用一个类中的方法来创建子类,而是需要一个特殊的函数来创建——createClass,它创建一个table

表示新类,其中一个参数表示新类的所有基类。创建时,它会设置元表中__index元方法,多重继承正是在这个

__index元方法中完成的。

local function search(k, plist)     -- table plist中查找k

    for i=1,#plist do

        local v =plist[i][k]

        if v thenreturn v end

    end

end

function createClass(...)

    local c = {}            --创建新类

    local parents = {...}   -- 类在其父类列表中的搜索方法

    setmetatable(c,{__index= function(t, k)

                        return search(k, parents)

end})  

    c.__index = c -- 'c'作为其实例的元表

    functionc:new(o) -- 为这个新类定义一个新的构造函数

        o = o or{}

setmetatable(o,c)

        return o

    end

    return c    -- 返回新类

end

使用createClass的例子。假设有两个类,一个是前面提到的Account类;另一个是Named类,它有两个

方法setname getname

Named = {}

function Named:getname()

    return self.name

end

function Named:setname()

    self.name = n

end

要创建一个新类NamedAccount,同时从AccountNamed派生,只需调用createClass:

NamedAccount = createClass(Account, Named)如下要创建并使用实例:

account = NamedAccount:new{name = "Paul"}

print(account:getname())    --> Paul

因为需要搜索字段,多重继承的性能不如单一继承。一种改进性能的做法是将继承的方法复制到子类中。

通过这种技术,类的__index元方法如下所示:

setmetatable(c, {__index = function(t, k)

                    local v = search(k, parents)

                    t[k] = v    -- 保存下来,以备下次访问

                    return v

end})

 

16.4 私密性(privacy)

Lua在设计对象时,没有提供私密性机制。但可以用其它方法实现对象的访问控制;

这种实现不常用,只做基本的了解即可。这种做法的基本思想是通过两个table

表示一个对象,一个table用来保存对象的状态;另一个用于对象的操作。对象本身

通过第二个table来访问,即通过其接口的方法来访问。

示例——使用这种设计来表示一个银行账户:

function newAccount(initialBalance)

    local self ={balance = initialBalance}

    local withdraw = function(v)

            self.balance = self.balance - v

        end

    local deposit= function(v)

            self.balance= self.balance + v

        end

    local getBlance = function()

        return self.balance

    end

 

    return {

        withdraw = withdraw,

        deposit =deposit,

        getBalance= getBalance

    }

end

这种设计给予存储在self table中所有东西完全的私密性。当newAccount返回后,就无法直接访问这个table了。

只能通过newAccount中创建的函数来访问它。

 

16.5 单一方法(single-method)做法

面向对象编程的做法有一种特殊情况,就是当一个对象只有一个方法时,可以不用创建接口table,但要将

这个单独的方法作为对象表示来返回。参阅7.1节;

单一方法对象还有一种情况,若这个方法是一个调度(dispatch)方法,它根据某个参数来完成不同的操作。

如想可以这样来实现一个对象:

function newObject(value)

    return function (action, v)

        if action == "get" then returnvalue

        elseif action == "set" then

value = v

        else

error("invalidaction")

        end

    end

end

如下所示:

d = newObject(0)

print(d("get"))     --> 0

d("set", 10)

print(d("get"))     --> 10

这种非传统的对象实现方式是很高效的。虽然无法实现继承,却拥有了完全的私密性控制。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值