• 【Lua 入门基础篇(十三)】面向对象


    在这里插入图片描述
    在这里插入图片描述

    一、面向对象

    面向对象编程(Object Oriented Programming,OOP)是一种非常流行的计算机编程架构。

    1. 特征

    • 封装: 指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
    • 继承: 继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
    • 多态: 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
    • 抽象: 抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。

    二、Lua 中面向对象

    Lua语言本身并没有提供面向对象的语法机制,这需要我们自己设计实现一套类的机制。首先,对于面向对象来说,我们至少需要类和对象这两个概念。

    我们知道,对象由属性和方法组成。Lua中最基本的结构是table,所以需要用table来描述对象的属性。

    Lua中的 function 可以用来表示方法。那么Lua中的类可以通过 table + function 模拟出来。

    类至少包含一个用于构造对象的方法。 对应到Lua上,就是一个代表类的table,它有一个构造函数,返回代表该类对象的table:

    Class = {}
    function Class:new()
        local o = {}
        return o
    end
    local object = Class:new()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1. 类的封装

    -- 元类
    rect = { area = 0, length = 0, breadth = 0 }
    -- 派生类的方法 new
    function rect:new(o, length, breadth)
        o = o or {}
        setmetatable(o, self)
        self.__index = self
        self.length = length or 0
        self.breadth = breadth or 0
        self.area = length * breadth
        return o
    end
    -- 派生类的方法 printArea
    function rect:printArea()
        print('rect\'s area = ', self.area)
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (1) 创建对象

    创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。

    r = rect:new(nil, 10, 20)
    
    • 1

    (2) 访问属性

    我们可以使用点号(.)来访问类的属性:

    print(r.length)
    
    • 1

    (3) 访问成员函数

    我们可以使用冒号 : 来访问类的成员函数:

    r:printArea()
    
    • 1

    内存在对象初始化时分配。


    2. 类的继承

    面向对象的一大特点就是继承,Lua的元表跟元方法也能模拟。

    【Person】:

    --[[
    	filename: '1_test.lua'
    ]]--
    Person = {
        age = 18,
        name = 'cauchy',
    }
    
    function Person:new(age, name)
        local o = {}
        setmetatable(o, self)
        self.__index = self
        o.age = age
        o.name = name
        return o
    end
    
    function Person:getAge()
        return self.age
    end
    
    function Person:printf()
        print(self.age, self.name)
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    Teacher继承了Person,并重写了Print()方法。

    【Teacher】:

    --[[
    	filename: '2_test.lua'
    ]]--
    require "1_test"
    
    Teacher = Person:new()
    
    function Teacher:new(age, name, course)
        local o = Person:new(age, name)
        setmetatable(o, self)
        self.__index = self
        o.course = course
        return o
    end
    
    function Teacher:printf()
        print(self.age, self.name, self.course)
    end
    
    local t = Teacher:new(30, 'Miss Liu', 'English')
    t:printf()
    print(t:getAge())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行结果:

    30		Miss Liu		English
    30
    
    • 1
    • 2

    Teacher并没有重写GetAge()方法,然而 t1:GetAge() 却正确的输出了30,所以是正确的继承了这个方法。
    而重写的printf()方法,多输出了一个course,也正常输出,可见重写也是可以的。


    3. 类的多态

    多态:同一个实现接口,使用不同的实例而执行不同的操作。

    Object = {} -- 所有对象基类: Object
    function Object:new() -- 实例化方法
        local obj = {}
        setmetatable(obj, self)
        self.__index = self
        return obj
    end
    
    function Object:create(cls) -- 继承
        _G[cls] = {} -- 大G表通过键值对,存储所有的全局变量
        local class = _G[cls]
        setmetatable(class, self)
        self.__index = self
        class.base = self -- 设置base属性,方便子类找父类
    end
    
    --[[
    	1. 生成GameObject类,为Object子类。
    	   (1) 两个属性:(posX, posY)
    	   (2) 一个方法:(move())
    --]]
    
    Object:create("GameObject") -- create object (object -> GameObject)
    GameObject.posX, GameObject.posY = 0, 0 -- set attribute
    function GameObject:move() -- set method
        print(string.format('move: (%s)', self))
        self.posX = self.posX + 1
        self.posY = self.posY + 1
        print("posX: ", self.posX, " posY: ", self.posY)
    end
    
    --[[
    	多态
    	1. 生成Player类,为GameObject子类。
    		(1) 重写move()方法
    --]]
    
    GameObject:create("Player") -- create object (GameObject -> Player)
    function Player:move() -- set method
    	--[[
    		这样调用即GameObject:move()
    		实例化出来的Player对象都共用GameObject的posX, posY。
    	--]]
        -- self.base:move()
    
    	--[[
    		这样实际传入的self是Player
    		但是Player里没有posX, posY属性,会去找GameObject,赋值自己的posX, posY,不会相互影响。
    	--]]
        self.base.move(self)
    end
    
    local p1 = Player:new()
    local p2 = Player:new()
    
    p1:move()
    p2:move()
    p1:move()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    运行结果:

    move: (table: 0x55fdb0457b70)
    posX:   1        posY:  1
    move: (table: 0x55fdb0457bb0)
    posX:   1        posY:  1
    move: (table: 0x55fdb0457b70)
    posX:   2        posY:  2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    三、方法访问权限(私有公有)

    冒号的作用是省略了self的传递,也就是说,如果我们不想写冒号改为写等号的话,那么每个方法的第一个参数必然是self。

    冒号的作用有两个:

    1、方法定义:会增加一个额外的隐藏形参(self)

    2、方法调用:会增加一个额外的实参(表自身)

    local t = { a = 1, b = 2 }
    function t:add() -- 使用 : 自定义函数
        return self.a + self.b
    end
    
    function t.sub(self) -- 使用 . 自定义函数
        return self.a - self.b
    end
    
    print(t:add())
    print(t.add(t))
    print(t.sub(t))
    print(t:sub())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上两种方法都可以说是Public方法。

    local function Console(self)  -- 私有方法 Console
        print(self.a, self.b)
    end
    
    • 1
    • 2
    • 3

    私有方法同样需要传递这个self,区别在于,私有方法是用local写的。跟其他语言不同之处在于,Lua中模拟的私有方法并没有确定的归属,换句话说,它只属于其所写的Lua文件,而不是写在文件中的某个Table表。

  • 相关阅读:
    html常见兼容性问题
    基于JavaSwing开发模拟电梯系统+分析报告 课程设计 大作业源码
    WWW 2019 | HAN:异质图注意力网络
    c++入门必学算法 并查集
    Python实现人工神经网络回归模型(MLPRegressor算法)并基于网格搜索(GridSearchCV)进行优化项目实战
    Android 10.0 framework层实现app默认全屏显示
    开快递驿站能赚钱么?去掉成本,一个月能赚多少钱?
    与六年测试工程师促膝长谈,他分享的这些让我对软件测试工作有了全新的认知~
    猿创征文 |【Linux】常用命令
    操作EXCEL计算3万条数据的NDVI并填入
  • 原文地址:https://blog.csdn.net/qq_52678569/article/details/125805146