lua面向對象類,繼承和多重繼承的實現

語法糖 在討論lua腳本的面向對象實現之前,我們先了解一個概念“語法糖(syntactic sugar)”,百度官方的解釋是:

也譯爲糖衣語法,是由英國計算機科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能並沒有影響,但是更方便程序員使用。通常來說使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機會

官方的東西一般都比較專業和嚴謹,但不好理解;我的看法就是語法糖是某種語法的別名,只是這個別名背地裏多做了些事情,所以我們用的時候就不用那麼麻煩。某種程度上可以理解爲宏,只是宏定義多了做了些事情。比如下面的宏

#include <stdio.h>
#define ARRAY_LEN(st)   do{ assert(st!=NULL); sizeof(st)/sizeof(st[0]) }while(0)
// ARRAY_LEN是求數組長度的宏,但我們額外加了assert判斷,排除st爲非法指針的情況

好了,這裏在提前說明兩個lua中用到的標點符號點號”.”和冒號“:”,冒號就是點號的語法糖,冒號比點號在背地裏多做了點事情,所以比點號多一個點,^_^。

舉例說名相同類的分別用“.”和“:”的實現方式

冒號

Class = {}
Class.__index = Class

function Class:new(x,y)
    local o = {len=1}
    setmetatable(o, Class)
    --[[o.x = x
        o.y = y--]]
    self.x = x
    self.y = y
    return o
end

function Class:test()
    print(self.x,self.y)
end

object = Class:new(10,20)
object:test()
object.test(object)

點號

Class = {}
Class.__index = Class

function Class.new(self,x,y)
    local o = {len=1}
    setmetatable(o, Class)
    --[[o.x = x
        o.y = y--]]
    self.x = x
    self.y = y
    return o
end

function Class.test(self)
    print(self.x,self.y)
end

object = Class:new(10,20)
object:test()
object.test(object)

兩種方法運行的結果相同,如下

10  20
10  20

從上面兩個例子中可以看出,用點號“.”比用冒號“:”的函數參數要多一個self或者是實例本身(object),代碼上看顯然用冒號的要方便的多,實際冒號也是帶了self參數的,只是隱藏了,我們不需要關心。爲啥要使用self呢,在lua程序設計第二版中有提到當一項操作所作用的”接受者”,需要一個額外的參數來表示該接受者,這個接受者就是self。

採用冒號寫法的函數會多出一個隱藏參數self,這個self不屬於Lua的關鍵字,但在冒號定義下會由編譯器自動創建這個局部變量,如果不想用self這個名字,修改lua的源代碼重新編譯就可以了,只是這樣一樣,就不能用其它第三方庫了。self的意思也很好理解,就是這個table本身,相當於C++的this指針,self指向當前作用域的父表結構,通常在函數定義中使用。


我們在來分析一下,lua如何使用元表的定義實現了面向對象編程,實現的方法有很多種,網上搜每一篇的形式都不同,但基本的要點是相同的。就是利用了元表的特性,派生出子類。這裏以冒號的代碼來說明

Class = {}
Class.__index = Class 

function Class:new(x,y)
    local o = {len=1}
    setmetatable(o, Class)
    --[[o.x = x
        o.y = y--]]
    self.x = x
    self.y = y
    return o
end

function Class:test()
    print(self.x,self.y)
end

object = Class:new(10,20)
object:test()

下面詳細說明,會比較囉嗦

Class = {}
--定義table,即Class類,沒有成員,成員在後面new函數中添加;也可以在此處聲明
--如:Class = {x,y}
Class.__index = Class --定義元方法,這個可以放到new函數裏面
如
setmetatable(o, {__index=Class})
或者是
setmetatable(o, Class)
self.__index = self -- self可以用Class替代,是一個意思
function Class:new(x,y) --創建對象或子類的構造方法,函數名沒有要求,習慣用new
    local o = {len=1} --子類,可以爲空,也可以有自己的成員變量
    setmetatable(o, Class) --設置元表,目的是讓temp繼承Class
    self.x = x -- 父類新增成員x,並賦值
    self.y = y --同上
    return o --返回o,此時應當看做是返回類的對象
end  
--這樣就完成了一個類的構造函數,可以用此函數類生成類的對象
function Class:test()  
    print(self.x,self.y)
end
--[[Class的成員函數, o是Class的對象,打印對象的x和y值--]]

object = Class:new(10,20) --[[創建object對象,賦初值x=10 y=20--]]
object:test() --調用成員函數
object.test(object) --顯式的調用成員函數,用的是點,需要帶入object
             --這也證明test函數中self指的是調用者,此處是object

繼承
下面看一個改進的版本,比較完整的實例,含父類和子類

human = {name="", sex = "", age = 0} --基類,允許爲空
function human:new(n, s, a)
    local o = {
    name = n,
    sex = s,
    age = a
    }
    setmetatable(o , {__index = self})
    return o
end

function human:printattri()
   print(self.name, self.sex, self.age)
end

--派生類,子類
student = human:new() --無需帶參數
function student:new(n,s,a, sc) --重載父類的"構造函數",
                                --也可以不重載,沿用父類human的
   local o = {
    name = n,
    sex = s,
    age = a,
    score = sc
   }
   setmetatable(o, {__index = self})
   return o
end

--[[function student:printattri()
    print(self.name, self.sex, self.age, self.score)
end--]] -- 重載 父類的printattri函數

function student:printscore() --子類自有函數
   print(self.score)
end

--創建一個父類對象和子類對象

tony = human:new("Tony", "male", 10)
andy = student:new("Andy", "female", 12, 100)
--andy = student:new("Andy", "female", 12)

tony:printattri()
andy:printattri()
andy:printscore()

執行結果如下

Tony    male    10
Andy    female  12
100

student 是 human的派生類,構造方法new在子類student中可以重載(重寫),也可以不重載,父類的方法也是可以重載。這樣就實現了繼承,借用別人的話:Lua裏的繼承就是在別人的table裏查找自己不存在的字段,這裏的字段當然也包含函數,別忘了在lua中函數也是一種類型。

多重繼承
下面講講多重繼承,lua的單繼承如果說是在別的表中查找自己沒有的東西,那麼多重繼承就是查找多個表,即在多個表中找到自己需要的。

在介紹多重繼承之前,我們先看一幅圖,然後分享一個小故事
這裏寫圖片描述

小故事
故事是這樣的:話說科技發達了,克隆人小菜一碟,有a和b兩個男人,a有錢但身體不好,b身體好但沒錢。這兩人都找不到老婆又想要小孩,於是找到科學家”大勇”網名流浪先生,想要克隆小孩子。科學家的半吊子徒弟“wuli濤”操作失誤,將a和b的基因都丟到了基因組合機器裏面,結果克隆了一個混雜的child。這個child繼承了father a 和father b的所有東西,但小孩就這麼一個,這倆爹都說小孩是自己的,爭得頭破血流。科學家“流浪先生”沒辦法又找到叫做new的克隆工廠,把child小孩複製一下,這樣就不用爭了。於是又克隆出tony和andy兩位小朋友,但克隆的時候出了事故,多克隆了一個lucy,這貨比較倒黴,father a 和 father b都不要他。注:不要問我child去哪裏,有可能這貨被科學家徒弟私自藏起來了,沒事自己克隆玩^_^。

呵呵,算是自己瞎寫的微型小說吧,但這個故事大致說明了lua子類繼承多個父類的關係。

function search(classes, key)
    for i=1, #classes do
      local value = classes[i][key];
      if value ~= nil then
          return value;
       end
    end
end

function createclass(...)
    local fathers = {...};
    local child = {};

    --設置類的元素
    setmetatable(child , {
    __index = function(table, key)
                return search(fathers, key);
              end
    })

    --新增new函數,用於創建對象
    function child:new()
        local o = {};
        setmetatable(o, {__index=self});
        return o;
    end

    function child:howl()
       print("我爸是誰,天知道!");
    end

    --返回繼承了多個父類的子類
    return child;
end


father_a = {};
function father_a:car()
   print("我是你爹,你看我有寶馬!拿去用");
end

--[[function father_a:new()
   o = {};
   setmetatable(o, {__index=self});
   return o;
end--]] --這段代碼有無對結果都沒有影響

father_b = {};
function father_b:bike()
   print("我是你爹,你看我也有車!自行車給你用");
end

--[[function father_b:new()
    o = {};
    setmetatable(o, {__index=father_b})
    return o;
end--]]


local child = createclass(father_a, father_b);

print("一個娃,不夠分");
child:car();
child:bike();

print("***************************************")

local tony = child:new();
local andy = child:new();
local lucy = child:new();

tony:car();
print("tony的老爹有錢有汽車");
andy:bike();
print("andy的老爹沒錢有自行車");
lucy:howl();
print("lucy比較悲催");

執行的結果爲:

一個娃,不夠分
我是你爹,你看我有寶馬!拿去用
我是你爹,你看我也有車!自行車給你用
***************************************
我是你爹,你看我有寶馬!拿去用
tony的老爹有錢有汽車
我是你爹,你看我也有車!自行車給你用
andy的老爹沒錢有自行車
我爸是誰,天知道!
lucy比較悲催

簡單說明一下設計思路

  1. 要實現繼承多個類,實現的方式就是讓父類(table)包含多個需要繼承的類(table),然後通過seach來查找匹配;查找的參數是,父類fathers 和 key,這個key如果是子類調用父類的函數,那麼就是函數名。
  2. fathers裏包含所有需要繼承的類,參數用的是…,不定參數形式,可以有多個。
  3. createclass創建child, child和fathers是元表關係,child中沒有的東西,都可以去fathers中查找,比如代碼child:car(),是在fathers中查找car函數,顯然結果是找到了father a有car。
  4. createclass中new的作用是用於創建不同的對象,所以我們可以用child子類去做創建對象(table)的工作,new出多個對象。

多層次繼承
我們可以看到lua面向對象的實現最基礎的就是元表,利用setmetatable建立子類和父類的關聯關係,我們還可以思考一下三層繼承關係,即爺爺、父親、兒子(孫子)的繼承實現。

grandpa = {name="", sex = "", age = 0} --基類,允許爲空
function grandpa:new(n, s, a)
    local o = {
    name = n,
    sex = s,
    age = a
    }
    setmetatable(o , {__index = self})
    return o
end

function grandpa:printattri()
   print(self.name, self.sex, self.age)
end

--派生類
father = grandpa:new() --無需帶參數
function father:new(n,s,a,w)
   local o = {
    name = n,
    sex = s,
    age = a,
    wife = w
   }
   setmetatable(o, {__index = self})
   return o
end

function father:printwife()
   print("wife is "..self.wife)
end

son = father:new()
function son:new(n,s,a,f)
    local o = {
    name = n,
    sex = s,
    age = a,
    friend = f
    }
    setmetatable(o, {__index = self})
    return o
end

function son:printfriend()
    print("friend is "..self.friend)
end

--創建一個父類對象和派生類對象

tony = grandpa:new("Tony", "male", 60)
andy = father:new("Andy", "female", 30,"helen")
lucy = son:new("Andy", "female", 10,"lilei")

tony:printattri() -- 爺爺的方法,爺爺調用沒問題
andy:printattri() -- 爺爺的方法,父親調用沒有問題
andy:printwife() -- 父親的方法,父親調用沒有問題
lucy:printattri() -- 爺爺的方法,孫子調用沒有問題
lucy:printfriend() -- 孫子的方法,孫子調用沒有問題

lucy:printwife() -- 父親的方法,兒子調用,沒有老婆,打印輸出nil
andy:printfiend() -- andy老爹沒有朋友屬性,也沒有printfriend方法,
                  -- 調用兒子的printfriend,系統報錯誤

執行結果如下
源文件 class8.lua

Tony    male    60
Andy    female  30
wife is helen
Andy    female  10
friend is lilei
lua: class8.lua:30: attempt to concatenate field 'wife' (a nil value)
stack traceback:
    class8.lua:30: in function 'printwife'
    class8.lua:61: in main chunk
    [C]: ?

以上都是一些簡單的例子,再加上自己的一些理解,如果有錯,請指正。

多重繼承這塊,借鑑了別人的設計,附上鍊接,如有侵權,請留言。
http://www.jb51.net/article/55171.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章