本文簡述了一些循環依賴的知識~
之前工作中遇到了循環依賴的問題,在此簡單記錄一些相關的知識~
拿 Lua(5.3) 舉例,如果我們循環 require 子產品,就會觸發堆棧溢出錯誤:
-- module_1.lua
require("module_2")
return {}
-- module_2.lua
require("module_1")
return {}
-- test.lua
-- cause stack overflow here ...
require("module_1")
使用 Lua require 的子產品隻有兩種狀态: 未加載 和 已加載,并沒有所謂的 部分加載 的概念,這也導緻了 Lua require 不能處理循環依賴問題,類似的,我們也可以看看 C# 中涉及循環依賴的表現:
我們都知道 C# 中類的靜态構造函數在建立第一個類型執行個體或者引用類型任一靜态成員之前會被調用,據此,我們可以編寫兩個互相引用的靜态構造函數來進行循環依賴的測試:
class ClassA
{
public static int s_value;
static ClassA()
{
s_value = ClassB.s_value;
}
}
class ClassB
{
public static int s_value;
static ClassB()
{
s_value = ClassA.s_value;
}
}
static void Main(string[] args)
{
Console.WriteLine(ClassA.s_value);
Console.WriteLine(ClassB.s_value);
}
也許你會猜測上述代碼也會産生堆棧溢出之類的問題,但實際上,程式會正常輸出 0 0,原因在于 C# 并不會重複執行類的靜态構造函數,哪怕類的靜态構造函數還沒有執行完成(正在執行),簡單來說, C# 中類的靜态構造函數可以處理循環依賴的問題,隻是執行結果可能并不直覺:
(有興趣的朋友可以考慮看看下面這個測試程式的輸出結果)
class ClassA
{
public static int s_value = 1;
static ClassA()
{
s_value = ClassB.s_value;
}
}
class ClassB
{
public static int s_value = 2;
static ClassB()
{
s_value = ClassA.s_value;
}
}
static void Main(string[] args)
{
Console.WriteLine(ClassA.s_value);
Console.WriteLine(ClassB.s_value);
}
解決循環依賴的一個通用方法就是抽取公用子產品,讓循環依賴的子產品解耦,轉而共同依賴于這個公用子產品,舉例來說:
譬如 子產品 A 依賴于 子產品 B(需要使用 B.Func), 子產品 B 也依賴于 子產品 A(需要使用 A.Func),則我們抽取出 子產品 C(其具有 A.Func 和 B.Func 的功能),然後讓 子產品 A 和 子產品 B 去除互相依賴,轉而都去依賴 子產品 C.
更多資料
- Lua Circluar Require
- C# and beforefieldinit
- TYPE INITIALIZER CIRCULAR DEPENDENCIES