使用CoffeeScript中的Node.js需求和类来解决循环依赖关系

我想知道是否有一种方法可以在使用CoffeeScript类和super时候习惯性地避免Node.js require循环依赖的问题。 鉴于以下简化的CoffeeScript文件:

一杯咖啡:

 C = require './c' B = require './b' class A extends C b: B someMethod: -> super module.exports = A 

b.coffee:

 C = require './c' A = require './a' class B extends C a: A someMethod: -> super module.exports = B 

第一个显而易见的问题是,A和B之间存在循环依赖关系。无论哪个先评估,都将作为另一个参考。 为了在一般情况下解决这个问题,我可能会尝试在每个方面做这样的事情:

一杯咖啡:

 C = require './c' class A extends C module.exports = A B = require './b' _ = require 'underscore' _.extend A::, b: B someMethod: -> super 

这是一个黑客,但似乎是解决循环依赖关系的一种常见方式,通过移动module.exports之前的依赖项B的require 。由于CoffeeScript类不能重新打开 ,它然后使用一些extend调用(这可以是复制属性和方法的任何方式)到A.prototype (又名A:: A.prototype来完成这个类。 现在的问题是super只能在类声明的上下文中正常工作,所以这个代码不会被编译。 我正在寻找一种方法来保存super和其他CoffeScript类function。

有几个规范的方法来处理这个问题。 他们中没有一个在我看来是非常优秀的。 (Node 实际上需要支持在周期性的情况下实际用原始上下文replace原始上下文中的临时对象,这样做的好处是值得做一些丑陋的,冒险的V8欺骗,IMO。/ rant)

晚build设

你可以有一个“更高层次”的模块,也许是你的库的入口模块,它预先形成了相互依​​赖的东西的最终设置:

 # <a.coffee> module.exports = class A extends require './c' someMethod: -> super # <b.coffee> module.exports = class B extends require './c' someMethod: -> super # <my_library.coffee> A = require './a' B = require './b' Ab = new B Ba = new A module.exports = A: A, B: B 

可怕的是因为:现在你已经把更高级别模块中的问题混为一谈了,并且从有用的上下文中删除了这个设置代码(并且希望它仍然保持原样)。 。

dependency injection

我们可以通过将设置移回到每个单独的子模块的关注中来改善上述问题,并且仅将依赖性pipe理移除到更高级别的文件中。 依赖关系将被更高级别的模块获取(没有周期),然后根据需要传递:

 # <a.coffee> module.exports = ({B})-> -> # Each module, in addition to being wrapped in a closure-producing # function to allow us to close over the dependencies, is further # wrapped in a function that allows us to defer *construction*. B = B() class A extends require './c' b: new B someMethod: -> super # <b.coffee> module.exports = ({A})-> -> # Each module, in addition to being wrapped in a closure-producing # function to allow us to close over the dependencies, is further # wrapped in a function that allows us to defer *construction*. A = A() class B extends require './c' a: new A someMethod: -> super # <my_library.coffee> A = require './a' B = require './b' # First we close each library over its dependencies, A = A(B) B = B(A) # Now we construct a copy of each (which each will then construct its own # copy of its counterpart) module.exports = A: A(), B: B() # Consumers now get a constructed, final, 'normal' copy of each class. 

可怕的是因为:除此之外,在这个特定的情况下(!!?!)绝对是丑陋的,你只是将解决依赖问题的问题推给了消费者。 在这种情况下,这个消费者仍然是你自己,这样做是行得通的…但是现在,当你想通过require('my_library/a') 单独暴露A ,会发生什么? 现在你必须logging给消费者,他们必须用X,Y和Z依赖性来参数化你的子模块……等等,等等等等。 放下兔子洞。

不完整的类

所以,为了迭代上面的内容,我们可以通过直接在类上实现它来从消费者那里抽象出一些依赖性的混乱(因此也关注本地):

 # <a.coffee> module.exports = class A extends require './c' @finish = -> require './b' @::b = new B someMethod: -> super # <b.coffee> module.exports = class B extends require './c' @finish = -> require './a' @::a = new A someMethod: -> super # <my_library.coffee> A = require './a' B = require './b' module.exports = A: A.finish(), B: B.finish() 

可怕的原因是:不幸的是,这仍然给你的API增加了一些概念上的开销:“在使用A之前确保你总是调用A.finish() !”可能对你的用户不好。 同样,它可能会导致子模块之间难以维护的,难以维护的错误依赖关系:现在,A可以使用 B的元素,除了依赖于A的B部分(以及哪些部分可能保留在开发过程中不明显)。

解决循环依赖

我不能为你写这部分,但它是唯一的非可怕的解决scheme; 如果你给他们提出这个问题,任何一个节点程序员都会来找你。 我已经提供了Stack Overflow的精神假设,你知道你在做什么(并且有很好的理由有周期性的依赖关系,并且删除它们对于你的项目来说是非常不利的,上面列出的缺点)…但在所有情况下,最可能的情况是,您只需要重新devise您的架构,以避免循环依赖。 (是的,我知道这个build议很糟糕。)

祝你好运! (=