zoukankan      html  css  js  c++  java
  • 《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 
    ") 
    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"))


    没有实例,直接通过对象本身访问对象实现的方法。
  • 相关阅读:
    【转】Netty系列之Netty是什么
    【转】程序员技术练级攻略
    【转】Spring MVC 教程,快速入门,深入分析
    【转】Jqgrid学习之数据
    【转】Jqgrid学习之ColModel API
    【转】jqGrid学习之参数
    【转】jqGrid学习之安装
    go语言项目汇总
    33 Introducing the Go Race Detector
    32 Profiling Go Programs 分析go语言项目
  • 原文地址:https://www.cnblogs.com/zhong-dev/p/4044574.html
Copyright © 2011-2022 走看看