《Programming in Lua 3》读书笔记(十二)

日期:2014.7.14     

PartⅡ Object-Oriented Programming

Lua中实现面向对象编程。
“如同OOP对象,table拥有状态;如同OOP对象,table拥有标识符---self,用来与其他变量做区分,而且两个table拥有同样的值也是不同的object(对象),因为self的不同;如同OOP对象,table也有生命周期,这个生命周期与谁在何处创建table是保持独立的”

对象是拥有自己的运算操作的,table也有,如
e.g.
Account = {blanche = 0}
function Account.withdraw(v)
     Account.balance = Account.balance - v
end
上述的函数就是OOP中称呼的method(方法)。当然,上述的使用技巧是不可取的:在函数体内使用全局变量Account。这样会造成严重的后果,而且这样使用限制性太大,当我们改变了变量类型,这个操作就失效了。这种操作与面向对象编程中对象保持独立的生存周期这一原则相悖。
e.g.
a , Account = Account,nil
a.withdraw(100.00)          --error
因为我们将Account赋值为nil了,所以withdraw函数就会报错。

针对上述的操作改进:我们可以传递额外的参数,作为函数运算的对象
e.g.
function Account.withdraw(self,v)
     self.balance = self.balance - v
end
此时
a , Account = Account,nil
a.withdraw(100.00)          --ok

但是在大多数面向对象编程的语言中,一般都是隐藏我们上述用到的那个参数。Lua也能隐藏这个参数,这里就要使用到冒号操作符
e.g.
function Account:withdraw(v)
     self.balance = self.balance - v
end
当然,这里使用冒号操作符只是一个语法约定而已,没有额外的意思。我们可以用点号运算符定义一个函数然后用冒号运算符调用该函数,反之亦然
e.g.
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)

我个人还是觉得按套路来,遵循这种语法约定。


16.1 Classes
Lua中没有类(class)的概念,但是很容易模仿出类。参考了prototype-base language(面向原型编程)中prototype(原型)的相关技巧。在这种语言中,也是没有类,但是每个对象都拥有一个原型。在这种语言环境下要表现出类的概念,我们只需要为继承者创建一个唯一的对象作为原型。类和原型的目的都在于共享某些行为。

前面在讨论元表的时候有提到继承,因此假如现在有两个对象a和b,采用如下操作便可将b设置为a的原型:
e.g.
setmetatable(a,{__index = b})
执行了这个操作之后,假如我们访问a中的成员,在找不到的时候会访问b。

回到现在讨论的类,假如我们需要创建一个新的account,其行为与Account一样,在这里我们就可以考虑使用继承,使用 __index 元方法。在这里我们不需要额外创建一个新的table作为元表,可以直接将我们要继承的table设置为其元表:
e.g.
function Account:new(o)
     o = o or {}
     setmetatable(o,self)
     self.__index = self
     return o
end
这里使用到了前文提到的冒号操作符,默认使用了self参数。
此时
a = Account:new(balance = 0}
a:deposit(100.00)
我们新建了一个table  a,其元表为Account,又修改了其元方法__index 为Account 自身,当我们在a中寻找deposit的时候,找不到的时候会自动在Account中寻找,达到了继承的要求。
创建a的时候,将balance赋值为了0,假如不给其赋值,则会继承其默认值
b = Account:new()
print(b.balance)               --- 0     继承了Account的balance的值0


16.2 Inheritance
继承
Lua中实现继承还是比较容易的
e.g.
--基类
Account = {balance = 0 }
function Account:new(o)
     o = o or {}
     setmetatable(0,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 "xxx" end
     self.balance = self.balance - v
end

现在我们想写一个子类继承这个基类,然后能在子类中做进一步的修改,可以这样操作
SpecialAccount = Account:new()
执行以上操作之后,SpecialAccount 便是Account的一个实例了(--modify 应该是继承而非实例吧?),当我们执行一下操作:
s = SpecialAccount:new(limit = 1000.00}
SpecialAccount 从基类中继承了new这个方法,因为这里使用了冒号操作符,默认使用了SpecialAccount这个参数,因此此时s的元表是SpecialAccount。当我们试图访问s中不存在的元素的时候,便会去SpecialAccount中寻找,而从SpecialAccount中寻找不到的时,转而会去Account中寻找。
e.g.
s:deposit(100.00)
此时lua会在s、SpecialAccount、Account里面寻找deposit方法

我们可以在子类中重新定义从基类中继承的方法:
e.g.
function SpecialAccount:withdraw(v)
     if v - self.balance >= self.getLimit() then
          error"xx"
     end
     self.balance = self.balance - v
end
function SpecialAccount:getLimit()
     return self.limit or 0
end

此时,当我们调用s:withdraw的时候,lua会直接在SpecialAccount找到该方法,执行该方法内的操作。
而lua中有趣的一点是,不需要重新创建一个新的类来实现一个新的行为,可以直接在对象中实现该行为,如:
上文我们已经创建了SpecialAccount对象s,我们要在s中实现一个限制行为,限制每次的操作限额,我们可以这样实现:
e.g.
function s:getLimit()
     return self.balance * 0.10
end
这样,当我们调用s:withdraw的时候,条件判断getLimit会直接得到s已经定义的行为,而不会再去SpecialAccount中寻找。


16.3 Multiple Inheritance
多重继承

Lua中实现面向对象编程是有很多种途径的,上文中提到的使用 __index 元方法是一种便捷的方式。在不同的情况下需要选择不同的实现方式,在这里介绍的是一种能实现多重继承的方法。
这里也涉及到了使用__index 元方法,在该方法内使用一个函数。当table的元表的 __index 字段中有一个函数的时候,Lua都会调用该函数而不管有没有在该table中寻找到key。
多重继承的思想在于一个类可以有多个父类。因此我们就不能用类的方法来创建子类,而是定义一个函数来实现该功能--createClass,以父类作为参数来创建子类。这个函数创建一个table来代表新的类,然后设置元表的元方法__index 来实现多重继承。在这里有要注意的地方,类和父类的关系与类和实例的关系是有差异的,一个类不能同时成为其实例和其子类的元表
e.g.
--假定现在有两个类,之前的Account和现在的Named
Named = {}
function Named:getname()
     return self.name
end
function Named:setname(v)
     self.name = v
end

--在plist这个table中寻找k
local function search(k,plist)
     for i = 1,#plist do
          local v = plist[i][k]
          if v then return v end
     end
end

function createClass(…)
     local c = {}               --新的类
     local parents = { … }
     --从父类table中找到各个父类中的方法
     setmetatable(c,{ __index = function (t,k)
          return search(k,parents)
     end} )     --多重继承的技巧在于此处,__index 元方法是一个函数,该函数会从父类列表中寻找每个父类中的所有方法,这样就实现了多重继承
     --新的类成为其实例的元表
     c.__index = c
     --创建新的类的构造方法
     function c:new(o)
          o = o or {}
          setmetatable(o,c)
          return o
     end
     return c
end

现在我们就能创建一个多重继承的类了:
--多重继承,创建新的类
NamedAccount = createClass(Account,Named)

--创建和使用实例
account = NamedAccount:new{name = "abcd"}
print(account:getname())

上述的search函数一定程度上影响性能,以下是作者给的改进:
setmetatable(c,{ __index = function ( t,k )
          local  v = search(k,parents)
          t[k] = v
          return v
     end})

一种编程技巧,谨记!

16.4 Privacy
隐私

在已提到的对对象的设计中,并没有提供隐私机制。这是我们使用table来表现对象的结果,也是受影响与Lua本身排斥一些冗余、人为限制的功能。作者的建议是假如不想访问某些值,那么大可以不去访问就是。
Lua的目标是为开发者提供便利,提供多种技巧实现多数需求,尽管设计lua中的对象初衷是不提供隐私机制的,但是可以通过别的方法来实现这个需求——访问控制。这个用的比较少,但还是值得去了解和学习掌握的。
实现这个功能需求在于用两个table来表现对象:一个表示其状态,一个用来表示其操作行为。访问对象的时候通过第二个table进行访问,而对第一个table的设计也有一定的要求,该table并不是存储在别的table中,而是存储在该对象方法的closure中。以此重新设计Account
e.g.
function newAccount( initialBalance )
     local self = {balance = initialBalance}
     local withdraw = function ( v )
                         self.balance = self.balance + v
                    end

     local getBalance = function ( ... )
                         return self.balance
                    end

     return{
          withdraw = withdraw,
          deposit = deposit,
          getBalance = getBalance
     }
end

在这里该函数首先创建了一个table用来存储内部对象的状态,存储至一个局部变量self。然后该函数内部创建了对象的一系列方法。最后函数创建并返回了另外一个对象,该对象内部存储了实际上要实现的方法的名字。返回的这个新的table应该相当于上文提到的第二个table。这里的核心点在于:这些方法没有使用冒号操作符得到self这个额外的默认参数,而是直接使用了。现在我们可以以一下方式创建新的对象并使用其方法:
e.g.
acc1 = newAccount(100.00)
acc1.withdraw(40.00)
print(acc1.getBalance())

利用这种方式创建的table,我们是没有办法直接访问原table的,只能通过newAccount里面的方法来访问。这样就实现来我们想要的隐私功能。


16.5 The Single-Method Approach
单例的实现

e.g.
print("The Single-Method Approach \n") 
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)
print(d("get"))
d("set",10)
print(d("get"))


没有实例,直接通过对象本身访问对象实现的方法。

 

posted @ 2014-07-18 23:29  Le Ciel  阅读(185)  评论(0编辑  收藏  举报