解决与Node.js的循环依赖关系需要和CoffeeScript中的类 [英] Resolving circular dependencies with Node.js require and classes in CoffeeScript

查看:70
本文介绍了解决与Node.js的循环依赖关系需要和CoffeeScript中的类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否有办法在使用CoffeeScript类和 super <时,使用Node.js的 require 以惯用方式避免循环依赖的问题/ code>。鉴于以下简化的CoffeeScript文件:

I want to know if there is a way to idiomatically avoid issues with circular dependencies with Node.js's require while using CoffeeScript classes and super. Given the following simplified CoffeeScript files:

a.coffee:

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

class A extends C
    b: B

    someMethod: ->
        super

module.exports = A

b.coffee :

b.coffee:

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

class B extends C
    a: A

    someMethod: ->
        super

module.exports = B

第一个明显的这里的问题是A和B之间存在循环依赖关系。首先评估的是 {} 作为对另一个的引用。为了在一般情况下解决这个问题,我可能会尝试在每个上面做这样的事情:

The first obvious issue here is that there is a circular dependency between A and B. Whichever one evaluates first will have {} as a reference to the other. To resolve this in the general case, I might try to do something like this on each:

a.coffee:

C = require './c'

class A extends C

module.exports = A

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

_.extend A::,
    b: B

    someMethod: ->
        super

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

This is a bit of a hack, but seems to be one common way of resolving circular dependencies by moving the module.exports before the require for the dependency B. Since CoffeeScript classes can't be reopened, it then uses an extend call of some variety (this could be any way of copying properties and methods) onto A.prototype (aka A::) to finish the class. The problem with this now is that super only works properly in the context of the class declaration, so this code won't compile. I'm looking for a way to preserve super and other CoffeScript class functionality.

推荐答案

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

There's several canonical ways to handle this. None of them, in my opinion, particularly excellent. (Node really needs to support actually replacing the temporary object in the original context with the exported object, in cyclical situations. The benefits of that are worth doing some ugly, hacky V8 trickery, IMO. /rant )

你可以拥有一个更高级别的模块,也许你的图书馆的入口模块,预先形成相互依赖的东西的最终设置:

You could have a ‘higher-level’ module, perhaps the entry module to your library, preform the final setup of mutually-dependant things:

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

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

Horrible because: You've now conflated concerns in the higher-level module, and removed that setup-code from the context in which it makes sense (and in which it would hopefully remain maintained.) Great way to watch things get out of sync.

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

We can improve on the above by moving the setup back into the concern of each individual submodule, and only removing the dependency management into the higher-level file. The dependencies will be acquired by the higher-level module (with no cycles), and then passed around as necessary:

# <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。在兔子洞里面。

Horrible because: Well, besides it being absolutely ugly in this specific scenario (!!?!), you've just pushed the solving-the-dependency-problem issue ‘up the stack’ to a consumer. In this situation, that consumer is still yourself, which works out okay ... but what happens, now, when you want to expose A alone, via require('my_library/a')? Now you've got to document to the consumer that they have to parameterize your submodules with X, Y, and Z dependencies ... and blah, blah, blah. Down the rabbit-hole.

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

So, to iterate on the above, we can abstract some of that dependency mess away from the consumer by implementing it directly on the class (thus keeping concerns local, as well):

# <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部分。(以及哪些部分)这些在开发过程中可能仍然不明显。)

Horrible because: Unfortunately, this is still adding some conceptual overhead to your API: "Make sure you always call A.finish() before using A!" might not go over well with your users. Similarly, it can cause obscure, hard-to-maintain bug-dependencies between your submodules: now, A can use elements of B ... except parts of B that depend on A. (And which parts those are, is likely to remain non-obvious during development.)

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

I can't write this part for you, but it's the only non-horrible solution; and it's the canonical one any Node programmer will come at you with, if you bring them this question. I've provided the above in the spirit of the Stack Overflow assumption that you know what you're doing (and have very good reason to have cyclical dependencies, and removing them would be non-trivial and more detrimental to your project than any of the downsides listed above) ... but in all reality, the most likely situation is that you just need to redesign your architecture to avoid cyclic dependencies. (Yes, I know this advice sucks.)

祝你好运! (=

这篇关于解决与Node.js的循环依赖关系需要和CoffeeScript中的类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆