编程小知识之循环依赖

2019-11-03 12:25:27 浏览数 (1)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。undefined本文链接:https://cloud.tencent.com/developer/article/1531827

之前工作中遇到了循环依赖的问题,在此简单记录一些相关的知识。

拿 Lua(5.3) 举例,如果我们循环 require 模块,就会触发堆栈溢出错误:

代码语言:javascript复制
-- module_1.lua
require("module_2")
return {}
代码语言:javascript复制
-- module_2.lua
require("module_1")
return {}
代码语言:javascript复制
-- test.lua
-- cause stack overflow here ...
require("module_1")

使用 Lua require 的模块只有两种状态: 未加载已加载,并没有所谓的 部分加载 的概念,这也导致了 Lua require 不能处理循环依赖问题,类似的,我们也可以看看 C# 中涉及循环依赖的表现:

我们都知道 C# 中类的静态构造函数在创建第一个类型实例或者引用类型任一静态成员之前会被调用,据此,我们可以编写两个相互引用的静态构造函数来进行循环依赖的测试:

代码语言:javascript复制
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# 中类的静态构造函数可以处理循环依赖的问题,只是执行结果可能并不直观:

(有兴趣的朋友可以考虑看看下面这个测试程序的输出结果)

代码语言:javascript复制
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

0 人点赞