Programming in Lua, 2Nd Edition - Chapter 16: Object-Oriented Programming

 

 

 

Chapter 16: Object-Oriented Programming

 

表是对象

 

表可以有自已的成员函数:

 

Account = {balance = 0}

function Account.withdraw (v)

       Account.balance = Account.balance - v

end

Account.withdraw(100.00)

 

但是在函数内部使用全局名Account 是不好的编程实践。

 

更灵活的方法:

 

Account = {balance = 0}

function Account.withdraw (self, v)

       self.balance = self.balance - v

end

a1 = Account

a1.withdraw(a1, 100.00)

-- 创建对象的方法有点麻烦,不过成员函数得以复用了

a2 = {balance=0, withdraw = Account.withdraw}

a2.withdraw(a2, 260.00)

 

self 参数的使用是面向对象语言的关键。Lua 中使用colon operator 隐藏self

 

使用colon operator 代替显示的写self

 

Account = {balance = 0}

function Account:withdraw (v)

       self.balance = self.balance - v

end

a1 = Account

a1:withdraw(100.00)

a2 = {balance=0, withdraw = Account.withdraw}

a2:withdraw(100.00)

 

 

我们可以以dot syntax 定义函数,然后以colon syntax 语法调用它。也可以以colon syntax 语法定义,以dot syntax 语法调用,只要正确的处理额外的self 参数。

 

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)

 

我们的对象有标识,状态,成员函数。但是还缺类系统,继承和私有机制。

 

 

16.1 Classes

 

类作为创建对象的模型。对象是类的实例。Lua 没有类的概念,但是可以模拟。

 

使用继承可以轻易实现原型。如果我们有ab 两个对象,所有我们要做的就是:

 

setmetatable(a, {__index = b})

 

new 一个对象

 

Account = {

       balance=0,

       withdraw = function (self, v)

              self.balance = self.balance - v

         end

}

 

function Account:new (o)

       o = o or {} -- create table if user does not provide one

       setmetatable(o, self)

       self.__index = self

       return o

end

 

function Account:deposit (v)

       self.balance = self.balance + v

end

 

Account.deposit(Account, 200.00)

Account:withdraw(100.00)

 

a = Account:new{balance = 0}

a:deposit(100.00)

 

lua 在表a 中找不到“deposit 项,所以在a的元表中找。

 

a:deposit(100.00) 大概等价于:

 

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

 

a 的元表是Account,而且Account.__index 也是Account(因为self.__index = self),所以前面的代码等价于:

 

Account.deposit(a, 100.00)

 

就是说,Lua 调用原始的deposit 函数,但是传递a 作为self 参数。所以新对象a 从对象Account 那里继承了deposit 函数。通过同样的机制,它可以继承Account 的所有东西。

 

不只是函数,一个对象还可以继承另一个对象的成员:

 

b = Account:new()     -- b.blance 是对Account.balance 的引用

print(b.balance) --> 0 

 

b balance 如果不存在就会去元表里找,结果就引用了Account.balance

 

deposit 方法的调用等价于:

 

b.balance = b.balance + v

 

右边的b.balance 是对Account.balance 的引用,整个语句执行完后b 就拥有自已的blance 域了。

 

16.2 Inheritance

 

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 = Account:new()

s = SpecialAccount:new{limit=1000.00}

s:deposit(100.00)

 

Lua s中找不到deposit 域,所以到SpecialAccount 中找;还是找不到,所以又去Account 中找。

 

SpecialAccount 特别之处在于我们可以重定义任意从基类继承来的函数。所要做的只是写一个同名的新方法:

 


 

函数重载

 

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 = Account:new()

 

function SpecialAccount:withdraw (v)

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

              error"insufficient funds"

       end

       self.balance = self.balance - v

end

 

function SpecialAccount:getLimit ()

       return self.limit or 0

end

 

s = SpecialAccount:new{limit=1000.00}

 

s:withdraw(200.00)

 

现在,当我们调用s:wthdraw(200.00)lua 不会进入Account,因为它首先在SpecialAccount中找到了新的withdraw 方法。

 


 

还可以再次在s 中重载:

 

function s:getLimit ()

       return self.balance * 0.10

end

 

 

16.3 Multiple Inheritance

 

因为对象不是lua 的原生类型(模似的),有许多方法在lua 中实现面象对向编程。我们已经看到过的,使用index 元方法是最好的简洁,高效,灵活的方法。虽然如此,还有其它一些方法,它们也许更适合一些特殊情况。下面,我们来看看lua 中如何实现多继承。

 

一个类不能同时做为其实例和其子类的元表。

 

多继承

 

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

 

Named = {}

 

function Named:getname ()

         return self.name

end

 

function Named:setname (n)

         self.name = n

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 = {} -- NamedAccount 就是c 的引用

         local parents = {...}

         -- class will search for each method in the list of its parents

         setmetatable(c, {__index = function (t, k)   -- 设置NamedAccount 的元表,元表有一个__index 函数,NamedAccount 中没有的东西就在__index 中找,既是在两个基类中找

                   return search(k, parents)

           end})

 

         c.__index = c

 

         function c:new (o)  -- NamedAccount new

                   o = o or {}

                   setmetatable(o, c)

                   return o

         end

         return c -- return new class

end

 

NamedAccount = createClass(Account, Named)

account = NamedAccount:new{name = "Paul"}  -- NamedAccount new 将表{name = "Paul"} 的元表设置为NamedAccount

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

 

NamedAccount 同时是Account Named 的子类。

 


 

当然,多继因为复杂的搜索会有性能上的损失。

 

一个简单的改善多继承方法是拷贝继承方法到子类

 

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

 

Named = {}

 

function Named:getname ()

       return self.name

end

 

function Named:setname (n)

       self.name = n

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 = {} -- NamedAccount 就是c 的引用

       local parents = {...}

       -- class will search for each method in the list of its parents

       setmetatable(c, {__index = function (t, k)   -- 设置NamedAccount 的元表,元表有一个__index 函数,NamedAccount 中没有的东西就在__index 中找,既是在两个基类中找

              local v = search(k, parents)

              t[k] = v  -- 将基类的方法拷贝到本地 -- save for next access

              return v

         end})

 

       c.__index = c

 

       function c:new (o)  -- NamedAccount new

              o = o or {}

              setmetatable(o, c)

              return o

       end

       return c -- return new class

end

 

NamedAccount = createClass(Account, Named)

account = NamedAccount:new{name = "Paul"}  -- NamedAccount new 将表{name = "Paul"} 的元表设置为NamedAccount

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

 

使用这种技巧,访问继承方法和本地方法一样快(除了第一次访问)。

 

 

16.4 Privacy

 

Lua 不支持私有机制,部分原因是因为使用表来表示对象。但是,这也反映了lua 背后的基础设计决策,设计大规模应用程序并不是Lua 的初衷。

 

虽然Lua 不支持私有机制,但是对象可以有不同的实现,所以我们可以对访问进行控制。虽然这种实现不常使用,而了解它是有益的。有两个原因,一是因为它展示了Lua 有趣的“犄角旮旯”,二是因为它可以成为一个在解决其它问题时很好的方案。

 


 

这种设计的基本想法是将每一个对象用两个表来表示:一个作为它的状态,另一个作为它的操作或称为接口。对象本身通过第二个表访问。为避免未授权的访问,对象中表示状态的那个表不是其它表的一个域,而是一个特定方法里面的一个闭包。

 

用闭包实现对象

 

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 getBalance = function () return self.balance end

       return {

              withdraw = withdraw,

              deposit = deposit,

              getBalance = getBalance

         }

end

 

acc1 = newAccount(100.00)

acc1.withdraw(40.00)

print(acc1.getBalance()) --> 60

 

首先,函数创建一个用来存内部对象状态的表,并把它保存在一个局部变量self 中。然后,函数创建对象的方法。最后,函数创建并返回一个包含了方法引用的表。这里的关键是这些方法不需要一个self 作为额外参数,而是直接访问self 本身。因为不再有外部参数,我们就不需要使用colon syntax 语法来操作对象了。方法的调用就像普通函数一样:

 

这个方案使得存在self 里面的任何东西都变成私有的了。当newAccount 返回后没有方法可以访问这个表,只能通过由newAccount 创建的方法访问。虽然这个例子只在私有表中放了一个实例变量,但我们可以将所有私有部分都放入这个表。我们也可以定义私有方法:它们和公有方法差不多,但是不放入接口中。

 

私有方法

 

function newAccount (initialBalance)

       local self = {

              balance = initialBalance,

              LIM = 10000.00,

         }

 

       local extra = function ()         -- 私有方法

              if self.balance > self.LIM then

                     return self.balance*0.10

              else

                     return 0

              end

         end

 

       local withdraw = function (v)

              self.balance = self.balance - v

         end

       local deposit = function (v)

              self.balance = self.balance + v

         end

 

       local getBalance = function ()

              return self.balance + extra()  -- 调用私有方法

         end

 

       return {

              withdraw = withdraw,

              deposit = deposit,

              getBalance = getBalance

         }

end

 

 

acc1 = newAccount(100.00)

acc1.withdraw(40.00)

print(acc1.getBalance()) --> 60

 

同样,这儿没有可供用户直接访问私有函数extra 的方法。

 


 

16.5 The Single-Method Approach

 

前面例子的一种特殊情况是,一个对象只有一个方法。这种时侯,我们就不需要去创建一个接口表了。我们返回这个方法作为对象表示。如果这听起来让人有点困惑,回想一下7.1 节是有益的。那里我们看到,我们如何创建一个用闭包来保存状态的迭代器。

 

单方法对象

 

function newAccount (initialBalance)

       local self = {

              balance = initialBalance,

              LIM = 10000.00,

         }

 

       local extra = function ()         -- 私有方法

              if self.balance > self.LIM then

                     return self.balance*0.10   -- 超过10000 %10 的加成效果

              else

                     return 0

              end

         end

 

       return function ()

              return self.balance + extra()  -- 调用私有方法

         end

end

 

 

acc1 = newAccount(11000.00)  -- 12100

print(acc1())  -- 对象的用法像C++ 的仿函数

 


 

另一个有趣的情况是单方法对象的方法实际上是一个分派方法,它基于不同的参数而执行不同的任务。这种对象的可能实现像这样:

 

基于不同参数而执行不同的任务的对象

 

function newObject (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(0)  -- 对象d 其实就是一个闭包

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、付费专栏及课程。

余额充值