-1

我想创建一个模块系统,您可以在其中以循环方式加载依赖项。原因是,在模型类之间的关系中,循环依赖无处不在,就像在 Ruby on Rails 中一样:

# app/models/x.rb
class X < ActiveRecord::Base
  has_many :y
end

# app/models/y.rb
class Y < ActiveRecord::Base
  belongs_to :x
end

这通常在 Ruby on Rails 模型中的工作方式,至少,它首先加载所有模型,依赖项首先是一个符号或“字符串”。然后一个集中管理器获取所有这些对象并将字符串转换为相应的类,现在这些类都已定义。当你设置第一个类与其对应的类的关系时,其余的类仍然与字符串有关,而这个第一个类与类有关,因此有一个过渡期,其中一些模型完全解析为类,而其他的仍然是字符串,因为算法正在运行并且尚未完成。一旦所有的字符串都被解析为它们对应的类,算法就完成了。

我可以想象一个变得更加复杂的世界,创建的循环不是一对一的循环依赖,而是在它们之间有很多层,创建一个 5 节点循环之类的东西。甚至是模块之间存在来回依赖关系的地方,例如:

# file/1.rb
class X
  depends_on A
  depends_on B
end
class Y
  depends_on B
end
class Z
  depends_on Q
end

# file/2.rb
class A
  depends_on Y
end
class B
  depends_on Z
end

# file/3.rb
class Q
  depends_on X
end

要解决此问题:

  1. 加载所有文件 *.rb,将depends_on其视为字符串。现在我们在其相应的模块中定义了每个类,但使用depends_onas 字符串而不是所需的类。
  2. 遍历文件并解析类。
  3. 对于文件 1.rb,解析 X... 等。
  4. X 依赖于 A,因此与 2/A 相关联。
  5. X 也依赖于 B,因此与 2/B 相关联。
  6. Y 取决于 B,因此与 2/B 相关联。
  7. Z 取决于 Q,因此与 3/Q 相关联。
  8. A 取决于 Y,因此与 1/Y 相关联。
  9. B 取决于 Z,因此与 1/Z 相关联。
  10. Q 取决于 X,因此与 1/X 相关联。
  11. 完毕。

所以基本上,有两个“回合”。第一轮是加载所有文件,并初始化类。第二轮是将班级成员与相应的其他班级相关联。顺序无所谓。

但它可以变得更复杂吗?需要两轮以上来解决这种循环依赖?我不确定。以这样的为例:

# file/1.rb
class A < A_P
  class AA < A_P::AA_P
    class AAA < A_P::AA_P::AAA_P

    end
  end
end
class B < B_Q
  class BB < B_Q::BB_Q
    class BBB < B_Q::BB_Q::BBB_Q

    end
  end
end

# file/2.rb
class A_P
  class AA_P < B
    class AAA_P < B::BB

    end
  end
end
class B_Q
  class BB_Q < A
    class BBB_Q < A::AA

    end
  end
end

在这种人为的情况下,您有:

  1. A(file/1) 取决于A_P(file/2),然后:
  2. AA(file/1) 取决于A_PAA_P,然后:
  3. AA_P(file/2) 取决于B(file/1),然后:
  4. B(file/1) 取决于B_Q(file/2) 等....

也就是说,似乎正在发生一些奇怪的事情。我不确定,我的头开始打结。

  1. 在完全解决类之前,您无法定义A正在扩展的类。在类完全解析之前,A_P您无法定义什么是类,这取决于被解析,这取决于被解析。ETC..AAAA_PBB_Q

是否有可能解决这种循环依赖?解决任意复杂循环依赖的一般算法是什么?这样,最终是所有循环依赖项都与实际值相关联,而不是字符串或其他表示实际值的符号。解决循环依赖的最终结果是每个引用都应该引用实际的对象。

它总是只是一个简单的两遍算法,首先加载基础对象,然后解析它们的依赖关系,将依赖关系的“字符串”转换为集合中的基础对象?

你能举出一个例子,它需要的不仅仅是简单的两遍算法吗?然后描述在这种情况下算法应该如何解决循环依赖?或者证明/解释如何确定只需要一个简单的两遍算法?

另一个例子可能是:

// ./p.jslike
import { method_x } from './q'
import { method_y } from './q'

function method_a() {
  method_x()
}

function method_b() {
  console.log('b')
}

function method_c() {
  method_y()
}

function method_d() {
  console.log('d')
}

// ./q.jslike
import { method_b } from './p'
import { method_d } from './p'

function method_x() {
  method_b()
}

function method_y() {
  method_b()
}

我想这也将是两次通过。

4

1 回答 1

0

您的问题的答案取决于您所说的“解决”是什么意思。

考虑以下情况:

您有三个类,ABC它们以循环方式相互依赖。

在第一次通过(加载)之后,您可以访问关于每个类的所有信息,这些信息都写入源文件中。

现在,假设您要完全“解决” class A。假装你现在不关心其他课程。

仅给定来自文件的信息,是否可以仅针对 ? 执行所需的“解决” A


如果答案是否定的,那么您的问题的答案也是否定的。

如果答案是肯定的,那么这意味着两次通过就足以完全解决每个类。您可以递归或迭代地执行此操作,并且您可能希望缓存完全解析的类以避免多次解析它们,尽管这不是正确性所必需的(这纯粹是性能优化)。

于 2021-11-10T19:13:57.923 回答