【读书笔记】语言特性-面向对象编程(五)

一. self(this)标识

        1.1 在此之前使用全局名称来创建方法

Account = {balance = 0}
function Account.withdraw(v)
    Account.balance = Account.balance - v
end
Account.withdraw(100.00)

                上述函数就是简单的方法实现,但是在函数中使用全局名称Account是一个非常糟糕的习惯,原因:

                1.       这个withdraw 函数只能针对 Account表(对象)工作 (不能给Account表之外的工作)

                2.        要withdraw 函数给Account表工作的话,也要满足对象保存在特定的全局变量时。如果我们改变了对象的名称,withdraw就不能工作(需要对象和函数之间存在相同的生命周期)

a, Account = Account, nil
a.withdraw(100)	-- ERROR

        1.2 使用self标识 优化上述方法

-- 添加一个self参数,调用此方法时需要指定操作对象
function Account.withdraw(self, v)
    self.balance = self.balance - v
end
a1 = Account
Account = nil
--...
a1.withdraw(a1, 100.00) -- ok

加入 self 作为函数的参数,调用时指定操作的对象,此时 函数内部相当于:a1.balance = a1.balance - v , 将操作对象传入函数这样做,使得摒弃了之前 单一对象的调用

        1.3 冒号操作符:(语法糖)

                在上述函数定义和调用时,定义时需要显示声明self形参,导致在调用时,又需要增加一个实参,不方便

                冒号操作符的作用:在一个方法调用中增加一个额外的实参,或在方法定义中增加一个额外的隐藏形参

Account = {balance=0, 
    	withdraw = function(self, v) 
     		self.balance - v 
       	end}
        
function Account:deposit(v)
    self.balance = self.balance + v
end

Account.deposit(Account, 200.00)
Account:withdraw(100.00)

二. 类(class)

        1. 类的结构

                到目前为止,对象已经具有了 标识(self)、状态和对状态进行的操作,还缺类体系继承私有性,下面来学习这些。

        2. 类体系(通过元表和元方法实现)

                    要在Lua语言中表示一个类,我们只需要创建一个专门被用作其他对象的原型对象即可。类和原型都是一种组织多个对象间共享行为的方式

                2.1 需求:如果有A,B两个对象,要B称为A的一个原型,A可以在B中查找它没有的操作。简单的说,B是A的类

setmetatable(A,{__index = B})

                2.2 以Account对象为例 ,使用__index元方法让新对象继承Account的操作

Account = {balance=0, 
    	withdraw = function(self, v) 
     		self.balance - v 
       	end}
        
function Account:deposit(v)
    self.balance = self.balance + v
end

Account.deposit(Account, 200.00)
Account:withdraw(100.00)
-- 创建元表mt,__index元方法为 Account表
local mt = {__index = Account}
function Account.new(0)
    o = o or {}
    setmetatable(0, mt)
    return o
end
a = Account.new{balance = 0}
a:deposit(100.00)

                当我们创建一个新账户a时,a会将mt作为其元表。当调用a:deposit(100.00)时,实际上调用的是a.deposit(a, 100.00), 不过,无法在表a中找到字段"deposit",所以它会在元表的__index中搜索。此时情况大致为:

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

                        2.3 优化2.2示例

                                1.不需要元表mt, 直接把Account用作元表

                                2.对new也用冒号语法

function Account:new(o)
    o = o or {}
    self.__index = self
    setmetatable(o, self)
    return o
end

三. 继承(Inheritance)

        3.1 需求:实现 SpecialAccount 继承Account, s 继承SpecialAccount

        

Account = {balance = 0}
function Account:new(o)
    o = o or {}
    self.__index = self
    setmetatable(o, self)
    return o
end

function Account:deposit(v)
    self.balance = self.balance + v
end

function Account:withdraw()
    if v > self.balance then error "insufficient funds" end
    self.balance = self.balance - v
end

                创建一个子类SpecialAccount 继承 Account,此时 new()中的self 元表指向 Account

SpecialAccount = Account:new()

                创建一个子类 s 继承 Account,此时new() 中的self元表都 指向SpecialAccount (谁调用就指向谁)

        

        3.2 重定义基类 Account 的 withdraw方法 (可以重定义基类的任意方法)

                由于子类SpecialAccount 继承自 Account, 只需要在 SpecialAccount 中重写编写 withdraw() 就好

-- 重定义withdraw
function SpecialAccount:withdraw(v)
    if v - self.balance >= self:getLimit() then
    	error "insufficient funds"
    end
    self.balance = self.balance - v
end
-- 当调用 s:withdraw(200.00)时,Lua语言会在SpecialAccount 中先找到 withdraw()方法,所以不会在Account中查找
s:withdraw(200.00)

四. 多重继承(一个类有多个超类)

-- 在表'plist'的列表中查找'k'
local function search(k, plist)
    for i = 1, #plist do
        local v = plist[i][k]	-- 寻找第‘i’个超类的k值
        if v then return v end
    end
end

function createClass(...)
    local c = {}		-- 创建一个局部空表
    local parents = {...}  -- 父类列表
    
    -- 给C 设置元表和元方法 __index, 当调用C表不存在的值时,会执行元方法 __index ,即执行 search, 在父类列表中找到一个符合的父类,返回结果
    setmetatable(c, {__index = function(t, k)
    	return search(k, parents) -- 因为 parents 是一个上值,所以可以访问到
    end})
    
    --c表设置元方法__index, 为后面给其他表做元表做准备
    c.__index = c
    
    -- 定义一个构造函数
    function c:new(o)
      o = o or {}
      -- 可以理解为,o继承于C
      setmetatable(o, c)
      return o
    end
    return c	
end

        新建一个只有两个方法 setname 和 getname 的类Named:

Named = {}
function Named:getName()
    return self.name
end
function Named:setname(n)
    self.name = n
end

        创建一个同时继承 Account 和 Named的新类 NamedAccount

NamedAccount = createClass(Account, Named)

account = NamedAccount:new{name = "Paul"}
print(account:getname()) -- > Paul

        那 account:getname() 是怎么进行求值的呢?

        首先,Lua语言 在 account 中没找到字段"getname", 它就找到 account 的元表的__index字段,示例中该字段为NameAccount, 然后 NameAccount也不存在字段 "NameAccount",

        那么,会从NameAccount的元表中查找 __index字段,由于该字段是个函数,Lua语言就调用这个函数 search。 然后先在 Account中查找 "getname",没有找到, 在 Named中找到了

五. 私有性

        Lua没有实现私有化的机制,但是Lua足够灵活,可以模拟出私有化机制,下面提供三种私有化方式

        1. 外部不去访问对象的内容就是私有的,哈哈,把 所有需要私有名称最后加一个下画线区分,例如:name_

        2. 在模块定义及返回时''做手脚''

        3. 对偶表示

function newAccount(initialBalance)
    local self = {balance = initialBalance}
    local withdraw = function(v)
    	self.balance = self.balance - v -- self是上值
    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

在函数newAccount 中 定义了几个闭包函数,和一个self 表,其中 那些闭包函数是公共的,self是私有的。因为 return 的时候压根不返回 self表而那些闭包函数,由于self是上值的原因,可以正常访问 self ,从而实现了 对外私有

        优化:在上面方法中,self表只有一个字段,其实也可以对函数私有

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

        创建 函数extra, 同样在return 时忽略它,实现外部私有

六. 单方法对象

        单方法对象是指:对象中只有一个方法的情况

        1. 这种情况可以不用创建接口表,只要把这个单独的方法以对象的表示形式返回

        2. 这个方法其实是一个根据不同参数完成不同任务的分发方法

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")) -- 0
d("set", 10)
print(d("gte")) -- 10

七. 对偶表示(实现私有性):每张表和其对应的默认值相互关联

        概念:

                通常,我们使用键把属性关联到表,例如:

table[key] = value

        换成对偶表示,则是

local key = {}
key[table] = value

        此时的key表为独立的表该表的键为 "各种表", 值为 "各种表"的 "默认值",上例中, 新建一个局部空表key,将 表table 和 它对应的默认值:value 保存在key表中。

local balance = {}
function Account.withdraw()
    balance[self] = balance[self] - v
end

        目的:用对偶表示的方式,保证了 访问 函数withdraw 时,由于balance是局部的,表的默认值都保存在 balance中,要想得到默认值,访问者还必须要能同时访问 表balance

        缺陷:

        当把 表(对象)作为 balance 表的键时,那么balance表就是永久存在,不会被垃圾收集器回收(后面会讲),直到某些代码将其从表中显示移除(简单来说,不会自动回收,在讲垃圾收集(第五章)时会优化)

        Demo:使用对偶表示实现账户(重构账户功能)

local balance = {}
Account = {}
function Account:withdraw(v)
    balance[self] = balance[self] - v
end
function Accont:deposit(v)
    balance[self] = balance[self] + v
end
function Account:balance()
    return balance[self]
end
function Account:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    balance[o] = 0 -- 初始余额
    return o
end
a = Account:new{}
a:deposit(100.00)
print(a:balance())	-- 100

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值