解决与Node.js的循环依赖关系需要和CoffeeScript中的类

Jor*_*dan 8 javascript circular-dependency require node.js coffeescript

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

一杯咖啡:

C = require './c'
B = require './b'

class A extends C
    b: B

    someMethod: ->
        super

module.exports = A
Run Code Online (Sandbox Code Playgroud)

b.coffee:

C = require './c'
A = require './a'

class B extends C
    a: A

    someMethod: ->
        super

module.exports = B
Run Code Online (Sandbox Code Playgroud)

这里的第一个显而易见的问题是A和B之间存在循环依赖关系.无论哪个首先评估,都将{}作为对另一个的参考.要在一般情况下解决这个问题,我可能会尝试在每个上执行以下操作:

一杯咖啡:

C = require './c'

class A extends C

module.exports = A

B = require './b'
_ = require 'underscore'

_.extend A::,
    b: B

    someMethod: ->
        super
Run Code Online (Sandbox Code Playgroud)

这有点像黑客攻击,但似乎是通过在依赖关系B module.exports之前移动来解决循环依赖关系的一种常见方法require.由于CoffeeScript类无法重新打开,因此它使用了extend一些变种(这可能是任何复制属性和方法的方法)A.prototype(也A::)完成课程.现在的问题是super只能在类声明的上下文中正常工作,因此这段代码不会编译.我正在寻找一种保存super和其他CoffeScript类功能的方法.

ELL*_*BLE 28

有几种规范方法可以解决这个问题.在我看来,他们都不是特别优秀.(节点确实需要支持在周期性情况下用导出的对象实际替换原始上下文中的临时对象.这样做的好处值得做一些丑陋,hacky的V8技巧,IMO./ rant)

后期施工

您可以拥有一个"更高级别"的模块,也许是您的库的入口模块,可以完成相互依赖的事物的最终设置:

# <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'

A.b = new B
B.a = new A

module.exports = A: A, B: B
Run Code Online (Sandbox Code Playgroud)

可怕的原因是:你现在已经将问题混淆在更高级别的模块中,并从其有意义的上下文中删除了该设置代码(并且希望它仍然保持不变.)观察事物不同步的好方法.

依赖注入

我们可以通过将设置移回到每个单独子模块的关注点来改进上述内容,并且仅将依赖关系管理移除到更高级别的文件中.依赖关系将由更高级别的模块获取(没有循环),然后根据需要传递:

# <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.
Run Code Online (Sandbox Code Playgroud)

可怕的原因是:嗯,除了它在这个特定场景中绝对丑陋(!!?!)之外,你只是把解决依赖问题问题"推向堆栈"给消费者.在这种情况下,那个消费者仍然是你自己,这可以解决...但是现在,当你想A 独自揭露,会发生什么require('my_library/a')?现在你必须向消费者记录他们必须使用X,Y和Z依赖项来参数化你的子模块......以及blah,blah,blah.在兔子洞下面.

不完整的课程

因此,为了重复上述内容,我们可以通过直接在类上实现它来从消费者那里抽象出一些依赖性混乱(从而保持对本地的关注):

# <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()
Run Code Online (Sandbox Code Playgroud)

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

解决周期性依赖关系

我不能为你写这部分,但它是唯一不可怕的解决方案; 如果你把这个问题带给他们,那么任何Node程序员都会为你提供规范.我已经在Stack Overflow假设的精神中提供了上述内容,你知道你正在做什么(并且有很好的理由拥有周期性的依赖关系,删除它们对你的项目来说是非常重要的,对任何上面列出的缺点)...但实际上,最可能的情况是你只需要重新设计你的架构以避免循环依赖.(是的,我知道这个建议很糟糕.)

祝你好运! (=

  • 哇,这是一个多么全面的答案.谢谢!很糟糕的是,我无法在动态添加到构造函数原型的方法中利用`super`,并且解析依赖关系将是非常重要的(在ORM中).我已经远离CoffeeScript,主要是由于持续的调试问题(即使使用源映射).在JS中,解决方案在添加原型方法之前基本上就像`util.inherits`和`module.exports`,并且只需要使用`SubClass._super.method`.结合CSRedux缺乏对"超级"的开发和支持,我宁愿停止依赖CS. (2认同)