## 所有類的基類 Object Lua 沒有嚴格的 oo(Object-Oriented)定義,可以利用元表特性來實現 先定義所有類的基類,即`Object`類。代碼順序從上到下,自成一體。[完整代碼](#oo.lua) 定義一個空表 `Object` ,`__index` 指向其自身(繼承將直接 ...
所有類的基類 Object
Lua 沒有嚴格的 oo(Object-Oriented)定義,可以利用元表特性來實現
先定義所有類的基類,即Object
類。代碼順序從上到下,自成一體。完整代碼
定義一個空表 Object
,__index
指向其自身(繼承將直接使用該表作為對象的元表)
Object = {}
Object.__index = Object
new
定義構造對象時的初始化行為,相當於構造器。基類不需要進行任何初始化操作
function Object:new()
end
extend
實現了類繼承,具體流程
- 創建一個空表
cls
,作為類 - 我們將父類的元方法全部複製給子類 ⭐為什麼
- 子類的
__index
指向其自身(子類可被繼承)(覆蓋了父類複製給子類的__index
) - 子類的
super
欄位指向父類 - 子類的元表指向父類(子類)
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
implement
用於實現介面類,可傳入多個介面
- 遍歷每個介面
cls
- 當前對象如果沒有實現介面類的某個方法,則將該方法的實現從介面類複製給對象
function Object:implement(...)
for _, cls in pairs({ ... }) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
is
用於判斷某個類或對象實例是否是另一個類
- 迴圈拿元表,直到沒有為止,最後一個元表一定是 Object
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
__tostring
用於對象 print
或 tostring
時自定義字元串化
function Object:__tostring()
return "Object"
end
直接用類名稱,來實現一個對象的實例化。__call
可以把變數當函數使用,比如Car
類(變數),local mycar = Car()
,生成了一個對象實例myCar
,屬於類Car
- 創建一個對象(空表),並把自身(類)作為對象的元表
- 執行構造器,由於對象是空表找不到,所以通過元表的
__index
也就是去父類找 - 返回初始化好的對象實例
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
全局函數 unrealized
用於模擬介面或抽象類未定義的方法,子類未實現時會寄
function unrealized(...)
error("未實現", 2)
end
到現在為止已經模擬了一個單繼承OO,在需要的地方導入模塊,使用 Object
和 unrealized
這兩個全局變數
實驗-抽象工廠
接下來實現抽象工廠模式。抽象工廠能創建一系列相關的對象,而無需指定其具體類。
考慮如下情況,有多類敵人(正方形、圓形、長條),敵人初始化是兩種狀態的一種(正常狀態,厚血狀態),且後期敵人和狀態種類還會增多
我們先定義敵人抽象類
Enemy = Object:extend()
Enemy.draw = unrealized
Enemy.new = function(self)
self.hp = 100
end
然後定義繼承抽象類Enemy
的抽象類SquareEnemy
,與繼承抽象類SquareEnemy
的兩個普通類SquareEnemyWhite
、SquareEnemyRed
。圓形敵人跟長條敵人同理。
SquareEnemy = Enemy:extend()
SquareEnemy.new = function(self, x, y, w)
SquareEnemy.super.new(self)
self.x = x
self.y = y
self.w = w
end
SquareEnemyWhite = SquareEnemy:extend()
SquareEnemyWhite.draw = function(self)
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle("fill", self.x, self.y, self.w, self.w)
end
SquareEnemyRed = SquareEnemy:extend()
SquareEnemyRed.new = function(self, ...)
SquareEnemyRed.super.new(self, ...)
self.hp = 200
end
SquareEnemyRed.draw = function(self)
love.graphics.setColor(1, 0, 0)
love.graphics.rectangle("fill", self.x, self.y, self.w, self.w)
end
定義工廠介面,在這裡介面算是一種特殊的抽象類(由於只能用表來模擬介面,所以讓介面也繼承Objcet)
IFactory = Object:extend()
IFactory.circleEnemy = unrealized
IFactory.squareEnemy = unrealized
IFactory.barEnemy = unrealized
分別實現白色工廠和紅色工廠(如果沒有額外的創建操作,可以不用return)
WhiteFactory = Object:extend()
WhiteFactory:implement(IFactory)
WhiteFactory.circleEnemy = function(...)
return CircleEnemyWhite(...)
end
WhiteFactory.squareEnemy = function(...)
return SquareEnemyWhite(...)
end
WhiteFactory.barEnemy = function(...)
return BarEnemyWhite(...)
end
RedFactory = Object:extend()
RedFactory:implement(IFactory)
RedFactory.circleEnemy = function(...)
return CircleEnemyRed(...)
end
RedFactory.squareEnemy = function(...)
return SquareEnemyRed(...)
end
RedFactory.barEnemy = function(...)
return BarEnemyRed(...)
end
接下來測試抽象工廠
require 'oo'
require 'enemy.aac'
require 'enemy.bar'
require 'enemy.circle'
require 'enemy.square'
require 'factory.aac'
require 'factory.red_factory'
require 'factory.white_factory'
enemies = {}
love.load = function()
IFactory = WhiteFactory()
table.insert(enemies, IFactory.circleEnemy(100, 100, 25))
table.insert(enemies, IFactory.squareEnemy(100, 200, 25))
table.insert(enemies, IFactory.barEnemy(100, 300, 10, 50))
IFactory = RedFactory()
table.insert(enemies, IFactory.circleEnemy(200, 100, 25))
table.insert(enemies, IFactory.squareEnemy(200, 200, 25))
table.insert(enemies, IFactory.barEnemy(200, 300, 10, 50))
for _, enemy in pairs(enemies) do
print(enemy.hp)
end
end
love.draw = function()
for _, enemy in ipairs(enemies) do
enemy:draw()
end
end
參考資料
- 《Lua程式設計·第四版》羅伯托·耶魯薩林斯希 、第227~241頁
其它
oo.lua
Object = {}
Object.__index = Object
function Object:new()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:implement(...)
for _, cls in pairs({ ... }) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__tostring()
return "Object"
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
function unrealized(...)
error("未實現", 3)
end
-- return Object
QUESTION1
如果不複製元方法,假設類B繼承類A,類B的對象實例b,b的元表是類B,在調用 b + b 時,涉及到算術運算符相關的元方法,b會在父類B中查找__add
,找不到並不會順著B的元表__index
再去B的父類A找,因此會報錯
A = {
__index = A,
__add = function(a, b)
return a.age + b.age
end,
name = "小白"
}
B = { __index = B, }
b = { __index = b, age = 1 }
setmetatable(B, A)
setmetatable(b, B)
print(b.name)
print(b + b)
--[[
> dofile 'TEST/test.lua'
小白
TEST/test.lua:15: attempt to perform arithmetic on a table value (global 'b')
stack traceback:
TEST/test.lua:15: in main chunk
[C]: in function 'dofile'
stdin:1: in main chunk
[C]: in ?
]]