我想知道在使用CoffeeScript类和require
时是否有办法习惯性地避免与Node.js super
的循环依赖性问题。给出以下简化的CoffeeScript文件:
a.coffee:
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之间存在循环依赖关系。无论哪个首先评估,都会{}
作为对另一个的引用。为了在一般情况下解决这个问题,我可能会尝试在每个上面做这样的事情:
a.coffee:
C = require './c'
class A extends C
module.exports = A
B = require './b'
_ = require 'underscore'
_.extend A::,
b: B
someMethod: ->
super
这有点像黑客攻击,但似乎是通过在module.exports
之前移动require
来解决循环依赖关系的一种常见方式。从CoffeeScript classes can't be reopened开始,它就是{{3}}然后在extend
(又名A.prototype
)上使用某种多样的A::
调用(这可能是复制属性和方法的任何方式)来完成课程。现在的问题是super
只能在类声明的上下文中正常工作,因此这段代码不会编译。我正在寻找一种方法来保留super
和其他CoffeScript类功能。
答案 0 :(得分:27)
有几种规范方法可以解决这个问题。在我看来,他们都不是特别优秀。 (节点真的需要支持在周期性情况下用导出的对象实际替换原始上下文中的临时对象。这样做的好处值得做一些丑陋,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
可怕因为:您现在已经将更高级别模块中的问题混为一谈,并从有意义的上下文中删除了该设置代码(并且希望它仍然存在保持。)观察事物变得不同步的好方法。
我们可以通过将设置移回到每个子模块的关注点来改进上述内容,并且只将依赖关系管理移除到更高级别的文件中。依赖关系将由更高级别的模块获取(没有循环),然后根据需要传递:
# <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.
可怕因为:嗯,除了它在这个特定场景中绝对丑陋(!!?!)之外,你只是推动了解决依赖问题的问题了堆叠'给消费者。在这种情况下,那个消费者仍然是你自己,这可以解决...但现在,当你想通过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()
可怕因为:不幸的是,这仍然会给您的API增加一些概念上的开销:“确保在使用A.finish()
之前始终致电A
!”可能不会过去与您的用户一起同样,它可能会导致子模块之间存在模糊,难以维护的错误依赖关系:现在,A可以使用 B的元素...除了依赖于A的B部分。(以及哪些部分)这些在发展过程中可能仍然不明显。)
我不能为你写这部分,但它是唯一不可怕的解决方案;如果你把这个问题带给他们,那么任何Node程序员都会为你提供规范的规范。我已经在Stack Overflow假设的精神中提供了上述内容,您知道自己正在做什么(并且非常好有理由拥有周期性依赖关系,并且删除它们将是非 - 比上面列出的任何缺点更重要且对你的项目更有害)...但在所有现实中,最可能的情况是你只需要重新设计你的架构以避免循环依赖。 (是的,我知道这个建议很糟糕。)
祝你好运! (=