尝试在 Lua 中实现面向对象的编程,但效果不佳

Trying to implement object-oriented programming in Lua, but it's not quite working

好的,所以我正在尝试按照此处的说明进行操作:https://www.lua.org/pil/16.1.html 在 Lua(以及 LOVE 游戏框架)中执行类似于 OO 编程的操作,但它不起作用。这是我的代码的核心。我有一个通用的 Object class:

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 class:

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

现在的问题是,我使用以下代码将其中两个 Ships 创建为另一个对象的成员:

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 的新实例分配给 ship1ship2,而是 直接分配给 self,出于我无法理解的原因。我是不是做错了什么或者这是解释器的一个奇怪的错误?

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

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

在我所有的 new 方法中将 o 设置为本地解决了所有问题。我不知道它到底是如何工作的,但我假设在设置元表时将它放在全局变量 space 中会破坏某些东西。

这不是解释器的错误,而是语言的设计行为。 o={} 创建全局变量,这不是程序员在这里期望的。 "Why it is so?" 是语言创造者经常被问到的问题。有 many efforts 可以更简单地控制该行为。

没有 local

o = {} 创建全局变量 全局变量可以在程序中的所有函数之间访问和共享,除非您使用花哨的环境范围技术。在函数内部使用全局变量为各种副作用打开了大门,你应该小心副作用。

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

Object.new = function(table_before_colon,arg)
    highlander = {} --global variable, there can be only one
    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()
--global highlander == Ship
--Ship.new points to Object.new

function Ship:load(arg)--equivalent to: Ship.load=function(self,arg)
    --code that sets fields of the `self` object and is called from within new
    self.x = arg.x or 0 -- equivalently highlander.x=arg.x or 0
end

现在,如果在 new 开始到 new returns 期间没有发生任何事情,那么全局变量的存在无关紧要。但是,显然,您的其他代码与此类似:

local OtherObject = Object:new() 
--otherObject ==  highlander

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()

returns 调用结束时的全局变量,在 [=18=20=] 调用中最后设置为 ship2 =] 内部调用 OtherObject:new.