面向对象闭包方法 |
|
这是将闭包用于对象的方案与极其简单的基于表的对象方案进行比较。在大多数情况下,将水手对象的函数作为元表的组成部分,以便每个水手实例不需要为所有函数使用哈希表,这将被视为惯用方式。这个关键的设计点意味着下面的内存比较毫无意义。与使用表实现对象的合理方法相比,闭包方法的内存开销显然会更不理想。
此页面描述了 面向对象教程 的替代方法。
请先阅读上面提到的页面,以了解替代方法的差异。
Lua 中最常见的 OOP 方式如下所示
mariner = {} function mariner.new () local self = {} self.maxhp = 200 self.hp = self.maxhp function self:heal (deltahp) self.hp = math.min (self.maxhp, self.hp + deltahp) end function self:sethp (newhp) self.hp = math.min (self.maxhp, newhp) end return self end -- Application: local m1 = mariner.new () local m2 = mariner.new () m1:sethp (100) m1:heal (13) m2:sethp (90) m2:heal (5) print ("Mariner 1 has got "..m1.hp.." hit points") print ("Mariner 2 has got "..m2.hp.." hit points")
以及输出
Mariner 1 has got 113 hit points Mariner 2 has got 95 hit points
我们实际上在这里使用冒号来将对象('self' 表)传递给函数。但我们必须这样做吗?
我们可以用不同的方式获得几乎相同的功能
mariner = {} function mariner.new () local self = {} local maxhp = 200 local hp = maxhp function self.heal (deltahp) hp = math.min (maxhp, hp + deltahp) end function self.sethp (newhp) hp = math.min (maxhp, newhp) end function self.gethp () return hp end return self end -- Application: local m1 = mariner.new () local m2 = mariner.new () m1.sethp (100) m1.heal (13) m2.sethp (90) m2.heal (5) print ("Mariner 1 has got "..m1.gethp ().." hit points") print ("Mariner 2 has got "..m2.gethp ().." hit points")
这里我们不仅封装了变量 `maxhp` 和 `hp`,还封装了对 `self` 的引用(注意 `function self.heal` 而不是 `function self:heal` - 没有更多 `self` 糖)。这是因为每次调用 `mariner.new ()` 时,都会构造一个新的独立闭包。很难不注意到除了访问私有变量 `hp` 之外,所有方法的性能都得到了提升(在第一种情况下,`self.hp` 比第二种情况下的 `self.gethp ()` 更快)。但让我们看看下一个例子。
-------------------- -- 'mariner module': -------------------- mariner = {} -- Global private variables: local idcounter = 0 local defaultmaxhp = 200 local defaultshield = 10 -- Global private methods local function printhi () print ("HI") end -- Access to global private variables function mariner.setdefaultmaxhp (value) defaultmaxhp = value end -- Global public variables: mariner.defaultarmorclass = 0 function mariner.new () local self = {} -- Private variables: local maxhp = defaultmaxhp local hp = maxhp local armor local armorclass = mariner.defaultarmorclass local shield = defaultshield -- Public variables: self.id = idcounter idcounter = idcounter + 1 -- Private methods: local function updatearmor () armor = armorclass*5 + shield*13 end -- Public methods: function self.heal (deltahp) hp = math.min (maxhp, hp + deltahp) end function self.sethp (newhp) hp = math.min (maxhp, newhp) end function self.gethp () return hp end function self.setarmorclass (value) armorclass = value updatearmor () end function self.setshield (value) shield = value updatearmor () end function self.dumpstate () return string.format ("maxhp = %d\nhp = %d\narmor = %d\narmorclass = %d\nshield = %d\n", maxhp, hp, armor, armorclass, shield) end -- Apply some private methods updatearmor () return self end ----------------------------- -- 'infested_mariner' module: ----------------------------- -- Polymorphism sample infested_mariner = {} function infested_mariner.bless (self) -- No need for 'local self = self' stuff :) -- New private variables: local explosion_damage = 700 -- New methods: function self.set_explosion_damage (value) explosion_damage = value end function self.explode () print ("EXPLODE for "..explosion_damage.." damage!!\n") end -- Some inheritance: local mariner_dumpstate = self.dumpstate -- Save parent function (not polluting global 'self' space) function self.dumpstate () return mariner_dumpstate ()..string.format ("explosion_damage = %d\n", explosion_damage) end return self end function infested_mariner.new () return infested_mariner.bless (mariner.new ()) end --------------- -- Application: --------------- local function printstate (m) print ("Mariner [ID: '"..m.id.."']:") print (m.dumpstate ()) end local m1 = mariner.new () local m2 = mariner.new () m1.sethp (100) m1.heal (13) m2.sethp (90) m2.heal (5) printstate (m1) printstate (m2) print ("UPGRADES!!\n") mariner.setdefaultmaxhp (400) -- We've got some upgrades here local m3 = mariner.new () printstate (m3) local im1 = infested_mariner.new () local im2 = infested_mariner.bless (m1) printstate (im1) printstate (im2) im2.explode ()
输出
Mariner [ID: '0']: maxhp = 200 hp = 113 armor = 130 armorclass = 0 shield = 10 Mariner [ID: '1']: maxhp = 200 hp = 95 armor = 130 armorclass = 0 shield = 10 UPGRADES!! Mariner [ID: '2']: maxhp = 400 hp = 400 armor = 130 armorclass = 0 shield = 10 Mariner [ID: '3']: maxhp = 400 hp = 400 armor = 130 armorclass = 0 shield = 10 explosion_damage = 700 Mariner [ID: '0']: maxhp = 200 hp = 113 armor = 130 armorclass = 0 shield = 10 explosion_damage = 700 EXPLODE for 700 damage!!
一切都相当自解释。我们以非常干净和快速的方式获得了所有常见的 OOP 技巧。
是时候战斗了。竞技场是 'Intel(R) Core(TM)2 Duo CPU T5550 @ 1.83GHz'。参赛者是
-- Table approach -------------------- -- 'mariner module': -------------------- mariner = {} -- Global private variables: local idcounter = 0 local defaultmaxhp = 200 local defaultshield = 10 -- Global private methods local function printhi () print ("HI") end -- Access to global private variables function mariner.setdefaultmaxhp (value) defaultmaxhp = value end -- Global public variables: mariner.defaultarmorclass = 0 local function mariner_updatearmor (self) self.armor = self.armorclass*5 + self.shield*13 end local function mariner_heal (self, deltahp) self.hp = math.min (self.maxhp, self.hp + deltahp) end local function mariner_sethp (self, newhp) self.hp = math.min (self.maxhp, newhp) end local function mariner_setarmorclass (self, value) self.armorclanss = value self:updatearmor () end local function mariner_setshield (self, value) self.shield = value self:updatearmor () end local function mariner_dumpstate (self) return string.format ("maxhp = %d\nhp = %d\narmor = %d\narmorclass = %d\nshield = %d\n", self.maxhp, self.hp, self.armor, self.armorclass, self.shield) end function mariner.new () local self = { id = idcounter, maxhp = defaultmaxhp, armorclass = mariner.defaultarmorclass, shield = defaultshield, updatearmor = mariner_updatearmor, heal = mariner_heal, sethp = mariner_sethp, setarmorclass = mariner_setarmorclass, setshield = mariner_setshield, dumpstate = mariner_dumpstate, } self.hp = self.maxhp idcounter = idcounter + 1 self:updatearmor () return self end ----------------------------- -- 'infested_mariner' module: ----------------------------- -- Polymorphism sample infested_mariner = {} local function infested_mariner_set_explosion_damage (self, value) self.explosion_damage = value end local function infested_mariner_explode (self) print ("EXPLODE for "..self.explosion_damage.." damage!!\n") end local function infested_mariner_dumpstate (self) return self:mariner_dumpstate ()..string.format ("explosion_damage = %d\n", self.explosion_damage) end function infested_mariner.bless (self) self.explosion_damage = 700 self.set_explosion_damage = infested_mariner_set_explosion_damage self.explode = infested_mariner_explode -- Uggly stuff: self.mariner_dumpstate = self.dumpstate self.dumpstate = infested_mariner_dumpstate return self end function infested_mariner.new () return infested_mariner.bless (mariner.new ()) end
和
-- Closure approach -------------------- -- 'mariner module': -------------------- mariner = {} -- Global private variables: local idcounter = 0 local defaultmaxhp = 200 local defaultshield = 10 -- Global private methods local function printhi () print ("HI") end -- Access to global private variables function mariner.setdefaultmaxhp (value) defaultmaxhp = value end -- Global public variables: mariner.defaultarmorclass = 0 function mariner.new () local self = {} -- Private variables: local maxhp = defaultmaxhp local hp = maxhp local armor local armorclass = mariner.defaultarmorclass local shield = defaultshield -- Public variables: self.id = idcounter idcounter = idcounter + 1 -- Private methods: local function updatearmor () armor = armorclass*5 + shield*13 end -- Public methods: function self.heal (deltahp) hp = math.min (maxhp, hp + deltahp) end function self.sethp (newhp) hp = math.min (maxhp, newhp) end function self.gethp () return hp end function self.setarmorclass (value) armorclass = value updatearmor () end function self.setshield (value) shield = value updatearmor () end function self.dumpstate () return string.format ("maxhp = %d\nhp = %d\narmor = %d\narmorclass = %d\nshield = %d\n", maxhp, hp, armor, armorclass, shield) end -- Apply some private methods updatearmor () return self end ----------------------------- -- 'infested_mariner' module: ----------------------------- -- Polymorphism sample infested_mariner = {} function infested_mariner.bless (self) -- No need for 'local self = self' stuff :) -- New private variables: local explosion_damage = 700 -- New methods: function self.set_explosion_damage (value) explosion_damage = value end function self.explode () print ("EXPLODE for "..explosion_damage.." damage!!\n") end -- Some inheritance: local mariner_dumpstate = self.dumpstate -- Save parent function (not polluting global 'self' space) function self.dumpstate () return mariner_dumpstate ()..string.format ("explosion_damage = %d\n", explosion_damage) end return self end function infested_mariner.new () return infested_mariner.bless (mariner.new ()) end
表方法的速度测试代码
assert (loadfile ("tables.lua")) () local mariners = {} local m = mariner.new () for i = 1, 1000000 do for j = 1, 50 do -- Poor mariner... m:sethp (100) m:heal (13) end end
闭包方法的速度测试代码
assert (loadfile ("closures.lua")) () local mariners = {} local m = mariner.new () for i = 1, 1000000 do for j = 1, 50 do -- Poor mariner... m.sethp (100) m.heal (13) end end
结果
tables: real 0m47.164s user 0m46.944s sys 0m0.006s closures: real 0m38.163s user 0m38.132s sys 0m0.007s
表方法的内存使用测试代码
assert (loadfile ("tables.lua")) () local mariners = {} for i = 1, 100000 do mariners[i] = mariner.new () end print ("Memory in use: "..collectgarbage ("count").." Kbytes")
闭包方法的内存使用测试代码
assert (loadfile ("closures.lua")) () local mariners = {} for i = 1, 100000 do mariners[i] = mariner.new () end print ("Memory in use: "..collectgarbage ("count").." Kbytes")
结果
tables: Memory in use: 48433.325195312 Kbytes closures: Memory in use: 60932.615234375 Kbytes
没有赢家,也没有输家。让我们看看我们最终得到了什么……
+---------------------------------------+---------------------------+-------------------------------------------------+ | Subject | Tables approach | Closured approach | +---------------------------------------+---------------------------+-------------------------------------------------+ | Speed test results | 47 sec. | 38 sec. | | Memory usage test results | 48433 Kbytes | 60932 Kbytes | | Methods declaration form | Messy | More clean | | Private methods | Fine | Fine | | Public methods | Fine | Fine | | Private variables | Not available | Fine | | Public variables | Fine | Fine | | Polymorphism | Fine | Fine | | Function overriding | Ugly (namespace flooding) | Fine (if we grant access only to direct parent) | | Whole class definition (at my taste) | Pretty messy | Kinda clean | +---------------------------------------+---------------------------+-------------------------------------------------+
如您所见,两种方法在功能上非常相似,但在表示方面存在显著差异。方法的选择主要取决于程序员的美学偏好。我个人更喜欢对大型对象使用闭包方法,而对小型对象(只有数据,没有函数引用)使用表。顺便说一下,不要忘记元表。