首页 文章

解决Clojure循环依赖关系

提问于
浏览
15

我正在研究一些Clojure代码,它们在不同的命名空间之间存在一些循环依赖关系,而我正试图找出解决它们的最佳方法 .

  • 基本问题是我在其中一个文件中收到"No such var: namespace/functionname"错误

  • 我尝试了"declare"这个功能,但后来抱怨:"Can't refer to a qualified var that doesn't exist"

  • 我当然可以重构整个代码库,但是每次你有一个依赖关系来解决这个问题似乎都是不切实际的.....对于某些循环依赖的网络来说可能会非常难看

  • 我可以将一堆接口/协议/声明分离到一个单独的文件中,并且所有内容都引用了....但这似乎最终会变得混乱并破坏我具有相关功能的当前漂亮的模块化结构组合在一起

有什么想法吗?在Clojure中处理这种循环依赖的最佳方法是什么?

5 回答

  • 1

    我记得Clojure中有关命名空间的一些讨论 - 在邮件列表和其他地方 - 我必须告诉你,共识(以及AFAICT,当前Clojure设计的方向)是循环依赖是一个设计的呼声重构 . 偶尔可能会有变通方法,但是丑陋,可能会对性能造成问题(如果你让事情变得不必要“动态”),不能保证永久工作等等 .

    现在你说循环项目结构很好,模块化 . 但是,如果一切都取决于一切......为什么你会这么称呼呢?此外,如果您提前计划树状依赖关系结构,那么“每次有依赖关系来解决”都不应该经常发生 . 为了解决将一些基本协议等放在自己的命名空间中的想法,我不得不说很多时候我都希望项目能够做到这一点 . 我发现我能够浏览代码库并快速了解它正在使用哪种抽象方式,这非常有帮助 .

    总而言之,我的投票是重构 .

  • 24

    我有一些类似gui代码的问题,我最终做的是,

    (defn- frame [args]
      ((resolve 'project.gui/frame) args))
    

    这允许我在运行时解析调用,这是从帧中的菜单项调用,所以我100%确定帧是定义的,因为它是从帧本身调用的,请记住,resolve可能返回nil .

  • 0

    我经常遇到同样的问题 . 尽管许多开发人员不愿意承认这一点,但这是该语言中一个严重的设计缺陷 . 循环依赖是真实对象的正常条件 . 没有心脏,身体就无法生存,没有身体,心脏就无法生存 .

    可以在呼叫时解决,但这不是最佳的 . 假设你有一个API,作为api的一部分是错误报告方法,但是api创建了一个有自己方法的对象,这些对象需要报告错误,你有循环依赖 . 错误检查和报告功能将经常被调用,因此在调用它们时解析不是一种选择 .

    在这种情况下,大多数情况下,解决方案是将没有依赖关系的代码移动到可以自由共享的单独(util)命名空间中 . 我还没遇到过用这种技术无法解决问题的情况 . 这使得维护完整,功能性的业务对象几乎不可能,但它似乎是唯一的选择 . Clojure还有很长的路要走,它是一种能够准确建模现实世界的成熟语言,直到那时以不合逻辑的方式划分代码才是消除这些依赖关系的唯一方法 .

    如果Aa()依赖于Ba()和Bb()依赖于Ab(),唯一的解决方案是将Ba()移动到Ca()和/或Ab()移动到Cb(),即使C在技术上不存在于现实中 .

  • 9

    将所有内容移动到一个巨大的源文件,以便您没有外部依赖项,或者重构 . 就个人而言,我会选择重构,但是当你真正了解它时,它就是关于美学的 . 有些人喜欢KLOCS和意大利面条代码,所以没有品味 .

  • 13

    仔细考虑设计是件好事 . 循环依赖可能告诉我们,我们对重要的事情感到困惑 .

    这是我在一两种情况下用来解决循环依赖的技巧 .

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;; example/a.cljc
    
    (ns example.a
      (:require [example.b :as b]))
    
    (defn foo []
      (println "foo"))
    
    #?(
    
       :clj
       (alter-var-root #'b/foo (constantly foo))                ; <- in clojure do this
    
       :cljs
       (set! b/foo foo)                                         ; <- in clojurescript do this
    
       )
    
    (defn barfoo []
      (b/bar)
      (foo))
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;; example/b.cljc
    
    (ns example.b)
    
    ;; Avoid circular dependency.  This gets set by example.a
    (defonce foo nil)
    
    (defn bar []
      (println "bar"))
    
    (defn foobar []
      (foo)
      (bar))
    

    我从Dan Holmsand's code in Reagent学到了这个技巧 .

相关问题