非快取實作相當簡單。它在表格中使用 _parents = {} 成員來建立繼承鏈。
我通常為所有表格設定此後備標籤方法,這表示建立繼承就像建立具有適當成員的表格一樣簡單-- ******************************************************** -- index tag method AdvProtoIndex(t,f) -- -- This is a 'simple' version of the multiple inheritance -- tag method. It does not cache and thus it is quite slow. -- However, if you think something strange is happening, you -- can fall back to this version and see if the strangeness -- goes away. function AdvProtoIndex (t,f) if f == '_parents' then -- to avoid loop if (OldIndex) then return OldIndex(t,f) else return nil; end end local p = t["_parents"]; if (type(p) == 'table') then local cur_index = 1; -- start at 1 local cur_data; repeat cur_data = p[cur_index]; if (type(cur_data) == 'table') then local result = cur_data[f]; if (result ~= nil) then return result; -- we found a match end else if (OldIndex) then return OldIndex(t,f); else return nil; end end cur_index = cur_index + 1; -- do next parent until (cur_data == nil); return nil; -- we didn't find a match else return nil; end end
使用上述簡單實作,很容易建立會嚴重影響執行階段效能的繼承鏈,因為繼承方法呼叫或執行個體資料存取可能會導致 2n 次查詢,其中 n 是整個鏈中繼承的基底類別數目。a_base = { a_number = 1 } b_base = { b_number = 2 } ab_class = { _parents = { a_base, b_base } } print(ab_class.a_number); -- yields "1" print(ab_class.b_number); -- yields "2"
提供一個效能最佳化的實作,其功能大多相同,但速度大幅提升。
---------------------------------------------------------- -- AdvProtoIndexWithCache -- -- This inheritance tag method handles multiple inheritance via a -- "_parent = {}" table. It caches information in the top-level table -- to make lookups fast. -- -- Example: -- -- This tag method is applied to all tables, so all you have to do to -- get a magic inheritance table is to do this: -- -- BaseObj1 = { a_value = "a" } -- BaseObj2 = { b_value = "b" } -- MyClass = { _parents = { BaseObj2, BaseObj1 } } -- MyInstance = { _parents = { MyClass } -- function setupMultipleInheritenceForTag(a_tag) -- I like to setup my tag methods within a function because -- then stuff like these private declarations can be -- referenced with upvalues and disappear. :) local NIL_OBJECT = { magic_nil_object = 1} local SLOT_REF_TAG = newtag() local OldIndex = gettagmethod(tag({}),"index") local debug_mode = nil AdvProtoIndexWithCache = function (t,f, instance, depth) if (f == '_parents') or (f == '_slotcache') then -- to avoid loop if (%OldIndex) then return %OldIndex(t,f) else return nil; end end if instance == nil then -- we are the instance! instance = t end if depth == nil then depth = 0 end -- get out the parent table local p = rawgettable(t,"_parents") local cache = rawgettable(instance,"_slotcache"); if cache then local item = rawgettable(cache,f) if item then if item == %NIL_OBJECT then return nil elseif tag(item) == %SLOT_REF_TAG then return item.obj[f] else return item end end else -- if we are the instance AND we had a _parents -- slot, then create the slot cache! if (instance == t) and (p) then cache = {} rawsettable(t,"_slotcache",cache); -- make the slot cache! end end if (type(p) == 'table') then local cur_index = 1; -- start at 1 local cur_data; repeat cur_data = p[cur_index]; if (%debug_mode) then print("---------", cur_index, depth) end if (type(cur_data) == 'table') then if (%debug_mode) then printTables(cur_data) end -- local result = cur_data[f]; local result = rawgettable(cur_data,f); if (%debug_mode and (result ~= nil)) then print("value: ", result) end -- if we found the slot in us, then we need -- to do the caching, because after we return -- it's not possible to make a SLOT_REF if ((result ~= nil) and (cache)) then rawsettable(instance,f,result); end if (result == nil) then result = AdvProtoIndexWithCache(cur_data,f,instance, depth + 1) end if (result ~= nil) then if (%debug_mode and (result ~= nil)) then print("value: ", result) end return result; -- we found a match end else local result if (%OldIndex) then result = %OldIndex(t,f); else result = nil; end if cache then if (type(result) == "function") then rawsettable(cache,f,result); elseif result == nil then rawsettable(cache,f,%NIL_OBJECT) else local slot_ref = { obj = cur_data } settag(slot_ref,%SLOT_REF_TAG); rawsettable(cache,f,slot_ref); end end return result; -- we found a match end cur_index = cur_index + 1; -- do next parent until (cur_data == nil); if cache then rawsettable(cache,f,%NIL_OBJECT) end return nil; -- we didn't find a match else return nil; end end settagmethod(a_tag,"index",AdvProtoIndexWithCache); -- Lua 3.1 method end
在快取中,函式會直接指向,而其他項目則使用稱為「SLOT_REF」的東西指向。SLOT_REF 是一種特殊類型的表格,它透過參照而非值進行快取。它包含對原始表格和原始值索引的參照,因此如果原始資料值變更,此 SLOT_REF 會正確指向新的資料值。方法不會這麼做,因為已決定方法查詢的效能比保留變更基底類別中方法的能力更重要。
這個機制的另一種實作方式可以更快速,方法是取消 SLOT_REF,改用某種方法來使 slotcaches 失效(例如維護一個反向 slotcache 相依性清單)。每當基底類別方法或函式變更時,反向相依性鏈的 slotcaches 就會失效。如果將「_slotcache」改為在物件的「類別」層級而非目前的「實例」層級發生,這可能會導致僅有適度的額外資料保留。
重要的是要了解,由於快取版本將資訊直接儲存在單一扁平表格中,因此如果基底類別方法已在快取表格中,變更這些方法可能會被忽略。在實務上,變更基底類別的方法是不常見的操作。使用這個機制時,應該完全避免這種做法。
由於 Lua(在我看來是錯誤地)將 nil 覆寫為 false 和空資料元素的意義,因此無法使用 nil 值覆寫繼承的物件成員。這是因為設定 self.a = nil 會導致移除「a」成員,進而導致遺失索引標籤方法觸發,而該方法會諮詢 _parents 或 _slotcache 表格以尋找繼承的元素「a」。我尚未找到解決這個問題的解決方法。
包含一些有用的公用程式函式的完整解決方案程式碼,請參閱這裡。