Lua面向?qū)ο缶幊淘斀?/h1>

前言

Lua并非嚴(yán)格意義上的面向?qū)ο笳Z言属划,在語言層面上并沒有直接提供諸如class這樣的關(guān)鍵字饭望,也沒有顯式的繼承語法和virtual函數(shù)相速,但Lua提供了一種創(chuàng)建這些面向?qū)ο笠氐哪芰Α?/p>

Lua面向?qū)ο缶幊毯喪?/p>

Lua中的table就是一種對象
1.table與對象一樣可以擁有狀態(tài)
2.table也是對象一樣擁有一個獨立于其值的標(biāo)識(一個self)
3.table與對象一樣具有獨立于創(chuàng)建者和創(chuàng)建地的生命周期
先看看以下代碼:

local tab1 = {a = 1, b = 2}
local tab2 = {a = 1, b = 2}
if tab1 == tab2 then
    print("tab1 == tab2")
else
    print("tab1 ~= tab2")   -->tab1 ~= tab2
end 

上述說明兩個具有相同值的對象(table)是兩個不同的對象坦胶。對象有其自己的操作惊窖,同樣table也有這些操作:

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

a = Account
Account = nil
a.withdraw(100.00)  --> attempt to index global 'Account' (a nil value)

上面的代碼創(chuàng)建了一個新函數(shù)敷扫,并將該函數(shù)存入Account對象的withDraw字段中哀蘑,然后我們就可以調(diào)用該函數(shù)了。不過葵第,這個特定對象還必須存儲在特定的全局變量中绘迁。如果改變了對象的名稱,withDraw就再也不能工作了卒密,出現(xiàn)attempt to index global 'Account' (a nil value)
這種行為違反了前面提到的對象特性缀台,即對象擁有獨立的生命周期。而我們可以通過增加一個參數(shù)來表示接受者哮奇,這個參數(shù)通常稱為self或this:

Account = {balance = 0}
function Account.withdraw( self, v )
    self.balance = self.balance - v
end
--基于修改后代碼的調(diào)用:
a1 = Account
Account = nil 
a1.withdraw(a1,100.00)
--正常工作膛腐。
print(a1.balance)  -->-100 

使用self參數(shù)是所有面向?qū)ο笳Z言的一個核心。大多數(shù)面向?qū)ο笳Z言都對程序員隱藏了self參數(shù)鼎俘, Lua只需要使用冒號哲身,則能隱藏該參數(shù),重寫為:

function Account:withdraw( v )
    self.balance = self.balance - v
end

調(diào)用時可寫為:

a1:withdraw(100.00)

冒號的作用是在方法定義中添加一個額外的隱藏參數(shù)贸伐,以及在一個方法調(diào)用中添加一個額外的實參律罢。冒號只是一種語法便利,并沒有引入任何新的東西棍丐。
現(xiàn)在的對象已有一個標(biāo)識误辑、狀態(tài)和狀態(tài)之上的操作。不過還缺乏一個類系統(tǒng)歌逢、繼承和私密性巾钉。

一個類就像是一個創(chuàng)建對象的模具。在Lua中則沒有類的概念秘案,不過要在Lua中去模擬類并不困難砰苍。在Lua中潦匈,要表示一個類,只需創(chuàng)建一個專用作其他對象的原型赚导。原型也是一種常規(guī)的對象茬缩,也就是說我們可以直接通過原型去調(diào)用對應(yīng)的方法。當(dāng)其它對象(類的實例)遇到一個未知操作時吼旧,原型會先查找它凰锡。
在Lua中實現(xiàn)原型是非常簡單的,比如有兩個對象a和b圈暗,要讓b作為a的原型掂为,只需要以下代碼就可以完成:

setmetatable(a, {__index = b})

在此以后,a就會在b中查找所有它沒有的操作员串。若將b稱為是對象a的“類”勇哗,就僅僅是術(shù)語上的變化。現(xiàn)在我就從最簡單的開始寸齐,要創(chuàng)建一個實例對象欲诺,必須要有一個原型,就是所謂的“類”渺鹦,看以下代碼:

local Account = {} -- 一個原型

好了瞧栗,現(xiàn)在有了原型,那如何使用這個原型創(chuàng)建一個“實例”呢海铆?接著看以下代碼:

--new可看作為構(gòu)造函數(shù)
function Account:new(o)  
     o = o or {}  -- 如果用戶沒有提供table迹恐,則創(chuàng)建一個
     setmetatable(o, self)
     self.__index = self
     return o
end

當(dāng)調(diào)用Account:new時,self就相當(dāng)于Account卧斟。接著殴边,我們就可以調(diào)用Account:new來創(chuàng)建一個實例了。再看:

local a = Account:new{balance = 100} -- 這里使用原型Account創(chuàng)建了一個對象
a:deposit(100.00)

上面這段代碼是如何工作的呢珍语?首先使用Account:new創(chuàng)建了一個新的實例對象锤岸,并將Account作為新的實例對象a的元表。再當(dāng)我們調(diào)用a:deposit(100.00)函數(shù)時板乙,就相當(dāng)于a.deposit(a)是偷,冒號就只是一個“語法糖”,只是一種方便的寫法募逞。我們創(chuàng)建了一個實例對象a蛋铆,當(dāng)調(diào)用deposit時,就會查找a中是否有deposit字段放接,沒有的話刺啦,就去搜索它的元表,所以纠脾,最終的調(diào)用情況如下:

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

a的元表是Account玛瘸,Account的__index也是Account蜕青。因此,上面的調(diào)用也可以使這樣的:

Account.deposit(a, 100.00)

所以糊渊,其實我們可以看到的是右核,實例對象a表中并沒有deposit方法,而是繼承自Account方法的渺绒,但是傳入deposit方法中的self確是a贺喝。這樣就可以讓Account(這個“類”)定義操作。除了方法芒篷,a還能從Account繼承所有的字段搜变。

繼承不僅可以用于方法采缚,還可以作用于字段针炉。因此,一個類不僅可以提供方法扳抽,還可以為實例中的字段提供默認(rèn)值篡帕。看以下代碼:

 --[[ 
 在這段代碼中贸呢,我們可以將Account視為class的聲明镰烧,如Java中的: 
 public class Account  
 { 
    public float balance = 0; 
    public Account(Account o); 
    public void deposite(float f); 
 } 
--這里balance是一個公有的成員變量。
 --]]
local Account = {balance = 0}

--new可以視為構(gòu)造函數(shù)
function Account:new(o) 
     o = o or {}  -- 如果用戶沒有提供table楞陷,則創(chuàng)建一個
     setmetatable(o, self)
     self.__index = self
     return o
end

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

local b = Account:new{} -- 這里使用原型Account創(chuàng)建了一個對象b
b:deposit() -->100
b:deposit() -->200

在Account表中有一個balance字段怔鳖,默認(rèn)值為0;當(dāng)我創(chuàng)建了實例對象b時固蛾,并沒有提供balance字段结执,在deposit函數(shù)中,由于b中沒有balance字段艾凯,就會查找元表Account献幔,最終得到了Account中balance的值,等號右邊的self.balance的值就來源自Account中的balance趾诗。調(diào)用b:deposit()時蜡感,其實就調(diào)用以下代碼:

b.deposit(b)

在deposit的定義中,就會變成這樣子:

b.deposit = getmetatable(b).__index.balance + 100

第一次調(diào)用deposit時恃泪,等號左側(cè)的self.balance就是b.balance郑兴,就相當(dāng)于在b中添加了一個新的字段balance;當(dāng)?shù)诙握{(diào)用deposit函數(shù)時贝乎,由于b中已經(jīng)有了deposit字段杈笔,所以就不會去Account中尋找deposit字段了。

繼承

由于類也是對象(準(zhǔn)確地說是一個原型)糕非,它們也可以從其它類(原型)獲得(繼承)方法蒙具。這種行為就是繼承球榆,可以很容易的在Lua中實現(xiàn)。
假設(shè)有一個基類Account:

local 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

現(xiàn)在需要從這個Account類派生出一個子類SpecialAccount 禁筏,則需要創(chuàng)建一個空的類持钉,從基類繼承所有的操作:

SpecialAccount = Account:new()

現(xiàn)在,我創(chuàng)建了一個Account類的一個實例對象篱昔,在Lua中每强,現(xiàn)在SpecialAccount 既是Account類的一個實例對象,也是一個原型州刽,就是所謂的類空执,就相當(dāng)于SpecialAccount 類繼承自Account類。再如下面的代碼:

s = SpecialAccount:new{limit=1000.00}

SpecialAccount 從Account繼承了new穗椅;不過辨绊,在執(zhí)行SpecialAccount :new時,它的self參數(shù)表示為SpecialAccount 匹表,所以s的元表為SpecialAccount 门坷,SpecialAccount 中字段__index的值也是SpecialAccount 。然后袍镀,我們就會看到默蚌,s繼承自SpecialAccount ,而SpecialAccount 又繼承自Account。當(dāng)執(zhí)行s:deposit(100.00)時,Lua在s中找不到deposit字段砌些,就會查找SpecialAccount ;如果仍然找不到deposit字段锦茁,就查找Account,最終會在Account中找到deposit字段绣硝◎呤疲可以這樣想一下,如果在SpecialAccount 中存在了deposit字段鹉胖,那么就不會去Account中再找了握玛。所以,我們就可以在SpecialAccount 中重定義deposit字段甫菠,從而實現(xiàn)特殊版本的deposit函數(shù)挠铲。

多重繼承

實現(xiàn)單繼承時,依靠的是為子類設(shè)置metatable寂诱,設(shè)置其metatable為父類拂苹,并將父類的__index設(shè)置為其本身的技術(shù)實現(xiàn)的。而多繼承也是一樣的道理痰洒,在單繼承中瓢棒,如果子類中沒有對應(yīng)的字段浴韭,則只需要在一個父類中尋找這個不存在的字段;而在多重繼承中脯宿,如果子類沒有對應(yīng)的字段念颈,則需要在多個父類中尋找這個不存在的字段。

Lua會在多個父類中逐個的搜索deposit字段连霉。這樣榴芳,我們就不能像單繼承那樣,直接指定__index為某個父類跺撼,而是應(yīng)該指定__index為一個函數(shù)窟感,在這個函數(shù)中指定搜索不存在的字段的規(guī)則。這樣便可實現(xiàn)多重繼承歉井。這里就出現(xiàn)了兩個需要去解決的問題:

保存所有的父類柿祈;
指定一個搜索函數(shù)來完成搜索任務(wù)。

-- 在多個父類中查找字段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 = {...}

    -- 類在其元表中搜索方法
    setmetatable(c, {__index = function (t, k) return search(k, parents) end})

    -- 將c作為其實例的元表
    c.__index = c

    -- 為這個新類建立一個新的構(gòu)造函數(shù)
    function c:new(o)
        o = o or {}
        setmetatable(o, self)

        -- self.__index = self 這里不用設(shè)置了酣难,在上面已經(jīng)設(shè)置了c.__index = c
        return o
    end

    -- 返回新的類(原型)
    return c
end

-- 一個簡單的類CA
local CA = {}
function CA:new(o)
    o = o or {}
    setmetatable(o, {__index = self})
    self.__index = self
    return o
end

function CA:setName(strName)
    self.name = strName
end

-- 一個簡單的類CB
local CB = {}
function CB:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function CB:getName()
    return self.name
end

-- 創(chuàng)建一個c類谍夭,它的父類是CA和CB
local c = createClass(CA, CB)

-- 使用c類創(chuàng)建一個實例對象
local objectC = c:new{name = "Paul"}

-- 設(shè)置objectC對象一個新的名字
objectC:setName("John")
local newName = objectC:getName()
print(newName)

使用createClass創(chuàng)建了一個類(原型)黑滴,將CA和CB設(shè)置為這個類(原型)的父類(原型)憨募;在創(chuàng)建的這個類(原型)中,設(shè)置了該類的__index為一個search函數(shù)袁辈,在這個search函數(shù)中尋找在創(chuàng)建的類中沒有的字段菜谣;
創(chuàng)建的新類中,有一個構(gòu)造函數(shù)new晚缩;這個new和之前的單繼承中的new區(qū)別不大尾膊,很好理解;
調(diào)用new構(gòu)造函數(shù)荞彼,創(chuàng)建一個實例對象冈敛,該實例對象有一個name字段;
調(diào)用object:setName(“John”)語句鸣皂,設(shè)置一個新的名字抓谴;但是在objectC中沒有這個字段,怎么辦寞缝?好了癌压,去父類找,先去CA找荆陆,一下子就找到了滩届,然后就調(diào)用了這個setName,setName中的self指向的是objectC被啼;設(shè)置以后帜消,就相當(dāng)于修改了objectC字段的name值棠枉;
調(diào)用objectC:getName(),objectC還是沒有這個字段泡挺。找吧术健,CA也沒有,那就接著找粘衬,在CB中找到了荞估,就調(diào)用getName,在getName中的self指向的是objectC稚新。所以勘伺,在objectC:getName中返回了objectC中name的值,就是“John”褂删。
飞醉。

私密性

我們都知道,在C++或Java中屯阀,對于類中的成員函數(shù)或變量都有訪問權(quán)限的缅帘。public,protected和private這幾個關(guān)鍵字還認(rèn)識吧难衰。那么在Lua中呢钦无?Lua中是本身就是一門“簡單”的腳本語言,本身就不是為了大型項目而生的盖袭,所以失暂,它的語言特性中,本身就沒有帶有這些東西鳄虱,那如果非要用這樣的保護的東西弟塞,該怎么辦?我們還是“曲線救國”拙已。思想就是通過兩個table來表示一個對象决记。一個table用來保存對象的私有數(shù)據(jù);另一個用于對象的操作倍踪。對象的實際操作時通過第二個table來實現(xiàn)的系宫。為了避免未授權(quán)的訪問,保存對象的私有數(shù)據(jù)的表不保存在其它的table中惭适,而只是保存在方法的closure中笙瑟。看一段代碼:

function newObject(defaultName)
     local self = {name = defaultName}
     local setName = function (v) self.name = v end
     local getName = function () return self.name end
     return {setName = setName, getName = getName}
end

local objectA = newObject("John")
objectA.setName("John") -- 這里沒有使用冒號訪問
print(objectA.getName())

這種設(shè)計給予存儲在self table中所有東西完全的私密性癞志。當(dāng)調(diào)用newObject返回以后往枷,就無法直接訪問這個table了。只能通過newObject中創(chuàng)建的函數(shù)來訪問這個self table;也就相當(dāng)于self table中保存的都是私有的错洁,外部是無法直接訪問的秉宿。大家可能也注意到了,我在訪問函數(shù)時屯碴,并沒有使用冒號描睦,這個主要是因為,我可以直接訪問的self table中的字段导而,所以是不需要多余的self字段的忱叭,也就不用冒號了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末今艺,一起剝皮案震驚了整個濱河市韵丑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虚缎,老刑警劉巖撵彻,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異实牡,居然都是意外死亡陌僵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門创坞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碗短,“玉大人,你說我怎么就攤上這事摆霉『来唬” “怎么了奔坟?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵携栋,是天一觀的道長。 經(jīng)常有香客問我咳秉,道長婉支,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任澜建,我火速辦了婚禮向挖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炕舵。我一直安慰自己何之,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布咽筋。 她就那樣靜靜地躺著溶推,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒜危,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天虱痕,我揣著相機與錄音,去河邊找鬼辐赞。 笑死部翘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的响委。 我是一名探鬼主播新思,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赘风!你這毒婦竟也來了表牢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤贝次,失蹤者是張志新(化名)和其女友劉穎崔兴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛔翅,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡敲茄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了山析。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堰燎。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖笋轨,靈堂內(nèi)的尸體忽然破棺而出秆剪,到底是詐尸還是另有隱情,我是刑警寧澤爵政,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布仅讽,位于F島的核電站,受9級特大地震影響钾挟,放射性物質(zhì)發(fā)生泄漏洁灵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一掺出、第九天 我趴在偏房一處隱蔽的房頂上張望徽千。 院中可真熱鬧,春花似錦汤锨、人聲如沸双抽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牍汹。三九已至琅翻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柑贞,已是汗流浹背方椎。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钧嘶,地道東北人棠众。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像有决,于是被迫代替她去往敵國和親闸拿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容