尝试在 Lua 中实现面向对象编程,但出现问题

好的,我试图遵循这里的指示: https://www.lua.org/pil/16.1.html,在 Lua (和 LOVE 游戏框架)中实现类似面向对象编程,但是出现了一些问题。这是我的代码核心部分。我有一个通用的 Object 类:

local Object = {}

function Object:load(arg)
end

function Object:update(dt)
end

function Object:draw()
end

function Object:new(arg)
    o = {}
    setmetatable(o, self)
    self.__index = self
    o:load(arg)
    return o
end

return Object

还有一个由它继承的 Ship 类:

Object = require('objects.object')

local Ship = Object:new()

Ship.sprite = love.graphics.newImage('assets/sprites/ship.png')
Ship.sprite:setFilter('nearest', 'nearest', 0)
Ship.px = Ship.sprite:getWidth()/2
Ship.py = Ship.sprite:getHeight()/2

function Ship:load(arg)
    self.x = arg.x or 0
    self.y = arg.y or 0
    self.sx = arg.sx or arg.s or 1
    self.sy = arg.sy or arg.s or 1
    self.rot = arg.rot or 0
    self.tint = arg.tint or {255, 255, 255}
end

function Ship:draw()
    love.graphics.setColor(self.tint)
    love.graphics.draw(self.sprite, self.x, self.y, self.rot,
                       self.sx, self.sy, self.px, self.py)
    love.graphics.setColor({255, 255, 255})
end

return Ship

现在的问题是,我用这段代码在另一个对象中创建了两个 Ship 的实例:

self.ship1 = Ship:new({x=50, y=self.y1, s=2, tint={0, 0.5, 1}})
self.ship2 = Ship:new({x=750, y=self.y2, s=-2, tint={1, 0.5, 0}})

但当我绘制它们时,我只看到了一个(第二个)。事实证明,就像上面的代码没有将新的 Ship 实例分配给 ship1 ship2 一样,而是直接分配给了 self,原因我无法理解。是我做错了哪些事情,还是解释器中的一个奇怪的 bug?

点赞
用户1697475
用户1697475

已解决!显然,需要这个小片段:

function Object:new(arg)
    local o = {}
    setmetatable(o, self)
    self.__index = self
    o:load(arg)
    return o
end

在所有的new方法中将o设置为局部变量解决了一切。我不知道它为什么可以工作,但是我假设当元表被设置时,把它放在全局变量空间中会破坏一些东西。

2018-05-20 11:34:27
用户4687565
用户4687565

这不是解释器的 bug,它是语言的设计行为。 o = {} 创建了全局变量,这不是程序员预期的结果。 "为什么会这样?" 是向语言创作者经常问的问题。有很多努力使得控制这种行为更简单。

o = {} 没有 local 创建全局变量,该全局变量在程序中的所有函数中都是可访问和共享的,除非使用精密的环境作用域技术。在函数内部使用全局变量会打开各种副作用的门,你应该小心副作用。

我删除了一些语法糖并添加了上面的代码,可以等价地编写如下:

Object.new = function(table_before_colon,arg)
    highlander = {} --全局变量,只能有一个
    setmetatable(highlander,table_before_colon);
    table_before_colon.__index = table_before_colon;
    highlander:load(arg) -- table_before_colon.__index.load(highlander,arg)
    return highlander
end

local Ship = Object:new()
-- 全局高地人 == Ship
--Ship.new 指向 Object.new

function Ship:load(arg)--等同于:Ship.load=function(self,arg)
    --设置“self”对象字段并从 new 中调用的代码
    self.x = arg.x or 0 -- 相当于 highlander.x=arg.x or 0
end

现在,如果从 new 开始到 new 返回的这段时间内没有对全局变量做任何操作,那么全局变量的存在就不再重要了。但是,显然,你的其他代码类似于这样:

local OtherObject = Object:new()
--otherObject ==  全局高地人

OtherObject.load = function(new_other_obj,arg)
    --highlander == new_other_obj
    new_other_obj.ship1 = Ship:new({x=50, y=self.y1, s=2, tint={0, 0.5, 1}})
    --highlander == new_other_obj.ship1
    new_other_obj.ship2 = Ship:new({x=750, y=self.y2, s=-2, tint={1, 0.5, 0}})
    --highlander == new_other_obj.ship2
end

因此,OtherObject.load 调用其他函数,那些函数也访问和修改相同的全局变量。

local some_object = OtherObject:new()

返回的是在调用结束时全局变量的值,这个全局变量最后设为 OtherObject.loadShip:new 调用中的 ship2,该调用是在调用 OtherObject.load 中的调用中完成的,这个调用又在 OtherObject:new 的调用中完成。

2018-05-22 16:12:34