本篇原預計為為第29天內容,原始標題為:「進階議題: 物件導向程式設計」。
如有發現自己穿越時空,或看不懂本文內容,屬於正常現象,請勿擔心。
您僅需要靜心等待,等待時間線補齊。
與ECMAScript相同,採用原形設計的物件導向。你可以與7天搞懂JS進階議題相互服用,可能會有意想不到的效果。
Lua並沒有直接保護內部資料的方法,你可能會需要使用 閉包 、 弱表 、 metatable 等來達成條件。
但今天只討論封裝裡最簡單的概念--集成,也是物件的基礎:將相關的物件、方法關連到一個結構裡面。沒錯,就是 table
,在本文中使用物件(object)一詞基本可視為同義。
Lua的
table
也確實很像是JS裡的Object
,但可能更像是Map
(一個ES6後出現的新類別)。
最基礎的資料結構概念是雜湊表。
建立物件(object
)就是建立表(table
),非常簡單:
coco = {
name = "桐生ココ",
age = 3501,
birth = {month = 6, day = 17},
slogan = "good morning mother f**ker",
say = function (self)
print(self.name .. ": " .. self.slogan)
end
}
coco:say() --> 桐生ココ: good morning mother f**ker
[^1]: 桐生ココ經典台詞取自: https://www.youtube.com/watch?v=EPz07MzczQE
但我們總不能每次都這樣直接建立物件,或許可以由一個加工工廠(函數)來處理?
function VTuber(options)
local vtuber = {
name = options.name or "",
age = options.age or 0,
birth = options.birth,
slogan = options.slogan or "...",
say = function (self)
print(self.name .. ": " .. self.slogan)
end
}
return vtuber
end
---------------------------
------- 建立實例 ----------
---------------------------
peko = VTuber {
name = "兎田ぺこら",
age = 111,
birth = {month = 1, day = 12},
slogan = "Peko↗ Peko↘ Peko↗ Peko↘ Peko↗ Peko↘~!?"
}
--------------------------
peko:say() --> 兎田ぺこら: Peko↗ Peko↘ Peko↗ Peko↘ Peko↗ Peko↘~!?
已經看過兩個最簡單建立物件的方法,其中第二個可能有更高的可讀性與靈活性後,接著要來說說Lua的繼承要怎麼實現。來說說metatable
裡可以設定的__index
。這個值可以是另一個表或是一個函數,他會在表沒有實際欄位時,從其值尋找,也就是從其表或是函數尋找。
極類似Python裡的
__getattr__
可以很間單的回傳default
字串看看:
setmetatable(coco, {
__index = function(key)
return "default"
end
})
print(coco.abc) --> default
print(coco.name) --> 桐生ココ
現在可以利用此特性來模擬繼承:
VTuber = {
say = function (self)
print(self.name .. ": " .. self.slogan)
end
}
VTuber_metatable = {
__index = VTuber
}
----------------------
coco.say = nil
peko.say = nil
setmetatable(coco, VTuber_metatable)
setmetatable(peko, VTuber_metatable)
---------------------
coco:say() --> 桐生ココ: good morning mother f**ker
peko:say() --> 兎田ぺこら: Peko↗ Peko↘ Peko↗ Peko↘ Peko↗ Peko↘~!?
當然這樣看下來只是建立一個類別而已,所以接下來要嘗試將VTuber
繼承YouTuber
這個類別:
YouTuber = {
name = "<<YouTuber Name>>",
youtube_channel = "https://youtube.com/"
}
setmetatable(VTuber, {__index=YouTuber})
print(coco.name .. " youtuber channel: " .. coco.youtube_channel) --> 桐生ココ youtuber channel: https://youtube.com/
這樣,所有VTuber
實例都繼承了YouTuber
的youtube_channel
預設值。當然也可以為不同實例使用不同的值:
coco.youtube_channel = "https://www.youtube.com/channel/UCS9uQI-jC3DE0L4IpXyvr6w"
print(coco.name .. " youtuber channel: " .. coco.youtube_channel)
--> 桐生ココ youtuber channel: https://www.youtube.com/channel/UCS9uQI-jC3DE0L4IpXyvr6w
現在來看看細部內容:有兩個VTuber
實例--coco
和peko
,這兩個實例的say
方法,實際上並不是本身擁有,而已經移到了VTuber
這個類別上。而這個類別繼承了YouTuber
這個類別,以至於所有實例直接擁有了yotube_channel
屬性。整體結構看起來會像是:
※ 注意的是:__index
並不直接在實例上,而是在metatable
上。
是不是也有些和ES6的
__proto
有點像阿?
我們已經讓實例共用部份屬性或方法了。接著,我們要來鎖定一些類別上的屬性:
CoverVTuber = {
company = "OVER株式会社"
}
setmetatable(CoverVTuber, {
__name = "class CoverVTuber",
__index = VTuber, -- CoverVTuber 繼承 VTuber
})
VTuber_metatable.__index = CoverVTuber -- 改變現有實例繼承類別為CoverVTuber
VTuber_metatable.__newindex = function (self, key, new_value)
local read_only<const> = { -- 標記唯讀屬性
company = true
}
if not read_only[key] then --> 如我非唯讀,才跟新欄位
rawset(self, key, new_value)
end
end
------------
print(coco.company) --> OVER株式会社
coco.company = "Hololive" --> company是唯讀,不會被改寫
print(coco.company) --> OVER株式会社
coco.a = "test" --> 不受到唯獨限制
print(coco.a) --> test
__newindex
只會有在是新的欄位時觸發,也就是已經有欄位的話,並不會受到唯讀的限制:
mano = {
name = "魔乃アロエ",
company = "OVER株式会社"
}
setmetatable(mano, VTuber_metatable)
print(mano.company) --> OVER株式会社
mano.company = "No"
print(mano.company) --> No
mano.company = nil -- 移除欄位
print(mano.company) --> 類別值:OVER株式会社
mano.company = "No" --> 視為設定新值,受到唯讀限制
print(mano.company) --> OVER株式会社
因為原本設定新值的方式會受到__newindex
影響,所以會需要使用更底層的方式指定值,也就是rawset
。這並不是經常使用的方式,但這裡有必要這樣做。
Lua提供了設計DSL的能力,我們可以看看羅塞塔提供的思路:
class "foo" : inherits "bar"
{
}
既然我主要想回去參考ECMAScript的物件系統,那麼我也將關鍵字改成extends
吧!
Class "foo" : extends "bar"
{
}
objects_metatable = {
-- 保存物件使用的metatable。
-- 同一類別的物件使用相同的metatable。
-- key值是使用的類別;value是metatable。
--------------------------
-- class = metatable
}
setmetatable(objects_metatable, {__mode="k"})
top_class = {} -- Class Type繼承的類別,擁有一些僅有Class Type可以使用的方法。
function top_class:extends(super_class)
-- extends關鍵字的處理
-- 僅有Class Type可以使用
assert(objects_metatable[self], "Must a Class")
assert(objects_metatable[_ENV[super_class]], "Super Class Must a Class")
getmetatable(self).__index = _ENV[super_class] -- 繼承
return self
end
-- method: new - to create new object
function top_class:new(options)
local object = options -- WARN: note: 這裡使用複製可能會更好。
for k, v in pairs(self) do
object[k] = options[k] or self[k]
end
setmetatable(object, objects_metatable[self]) -- 註冊物件使用的metatable。
return object
end
function Class(class_name)
assert(type(class_name) == "string", "class name must a string")
local class = {}
_ENV[class_name] = class -- regist to global environment
setmetatable(class, {
__name = "<"..class_name..">",
__index = top_class, -- 繼承top_class
__call = function (self, options) -- 使class結構可以直接被呼叫,更新類別結構
for k, v in pairs(options) do
self[k] = v
end
end
})
objects_metatable[class] = {
__index = class, -- 使實例物件繼承類別(指定類別)
__name = "<instance of " .. class_name .. ">",
}
return class
end
以上基本把Class
、extends
等關鍵字的行為處理完畢,接著來建立實際類別看看:
Class "YouTuber" {
name = "<<YouTuber Name>>",
youtube_channel = "https://youtube.com/"
}
Class "VTuber" :extends "YouTuber" {
name = "",
age = 0,
slogan = "...",
say = function (self)
print(self.name .. ": " .. self.slogan)
end
}
Class "CoverVTuber" : extends "VTuber" {
company = "OVER株式会社"
}
------------------
print(YouTuber) --> <YouTuber>: 0x5622d8d7e290
print(VTuber) --> <VTuber>: 0x5622d8d63c10
print(CoverVTuber) --> <CoverVTuber>: 0x5622d8d8eb30
貌似沒什麼問題呢❤️~
接著來建立實例使用看看吧!
coco = CoverVTuber:new {
name = "桐生ココ",
age = 3501,
birth = {month = 6, day = 17},
slogan = "good morning mother f**ker",
}
peko = CoverVTuber: new {
name = "兎田ぺこら",
age = 111,
birth = {month = 1, day = 12},
slogan = "Peko↗ Peko↘ Peko↗ Peko↘ Peko↗ Peko↘~!?"
}
hahama = CoverVTuber: new {
name = "赤井はあと",
age = 16,
birth = {month = 8, day = 10},
slogan = "哈恰瑪恰瑪", -- https://www.youtube.com/watch?v=hvoBS-BzB3M
}
-----------
coco:say() --> 桐生ココ: good morning mother f**ker
peko:say() --> 兎田ぺこら: Peko↗ Peko↘ Peko↗ Peko↘ Peko↗ Peko↘~!?
hahama:say() --> 赤井はあと: 哈恰瑪恰瑪
這樣實例的建立是否好理解多!
事件始末:
我不打算對此事有太深入的糾葛,但又認為沉默不是個好選擇。就簡單談談我對這件事情的一些個人看法吧!
我對於其作法覺得有問題也沒有問題。
沒有太大問題的部份是,如果懲處確實是合約裡禁止之事項,那麼不管是玩皮,還是公開後台數據,受到暫停直播的懲處,也是可以理解。
有問題的部份是其陰陽申明實在讓人看不懂這是什麼操作?這公關很像雙面人,反而會讓人更有不信任感吧!
雖然能理解,其旗下也有藝人生活於...大陸/中華人民共和國(不管你怎麼稱呼,反正地理位置就是那裡)。處理不當,也可能使其旗下其他藝人造成危險。但自己把政治議題扯到申明去...我是覺得不算很聰明(更何況還是陰陽申明)。如果真的是公開後台數據造成的問題的話,應該不用牽扯到政治吧?
處置方式:暫停直播,就目前看來也不是止血的好方法。
只想說...你出征對象應該是平台還是VTuber?是不是該先搞清楚阿?
不過YouTube聽說是不存在的網站...那裡面內容當然不會是服務給不存在的人使用吧?
這我就真不知道怎麼說了......
就我自己事後去看的片段與了解... ...也就只因為在影片說出某些字,就被出征了?然後又有某些字不能用?
說個小故事:
我有長輩很討厭聽到「幹」這個字。但這個字不只是壞的阿!主幹、幹部,更有重要的意思。 不能說不代表不存在 ,而且很有可能會創造出其他說法。
每個人都有錯,但只有愚者才會執迷不悟。
-- 西塞羅
如果只是禁止他,而不正視他,那能進步的機會我想非常有限...
我想不透這樣只是禁止某些字,除了自己爽以外能得到什麼。一個字的使用也與上下文有關,只因為某種使用方式不好就禁止,文化會不會開倒車呢?
欸?怎麼好像也可用在Cover上?
當然,就技術角度上來看,這是最簡單處理的辦法。
只是就那些腦袋玻璃做的的人說,不知道聽到「泰... ...
...
...
...國」,是不是也馬上可以聽到玻璃碎的聲音?
對此我只能....
哈洽馬真洗腦阿~
喔對!最後祝各位中秋節快樂阿!