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的单继承如果说是在别的表中查找自己没有的东西,那么多重继承就是查找多个表,即在多个表中找到自己需要的。

在介绍多重继承之前,我们先看一幅图,然后分享一个小故事
《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

    原文作者:炼气士
    原文地址: https://blog.csdn.net/ljd680/article/details/79218476
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞