我正在研究一些Clojure代码,它们在不同的命名空间之间存在一些循环依赖关系,而我正试图找出解决它们的最佳方法。
有什么想法?在Clojure中处理这种循环依赖的最佳方法是什么?
答案 0 :(得分:24)
我记得Clojure中有关名称空间的一些讨论 - 在邮件列表和其他地方 - 我必须告诉你,共识(以及AFAICT,当前Clojure设计的方向)是循环依赖是一个设计为重构而哭泣。偶尔可能会有变通方法,但是丑陋,可能会对性能造成问题(如果你让事情变得不必要“动态”),不能保证永远工作等等。
现在你说圆形项目结构很好并且模块化。但是,如果一切都依赖于一切......为什么你会这么称呼呢?此外,如果您提前计划树状依赖结构,那么“每次有依赖性来解决”都不应该经常发生。为了解决将一些基本协议等放在自己的命名空间中的想法,我不得不说很多时候我都希望项目能够做到这一点。我发现浏览代码库并了解它正在快速处理哪种抽象方式非常有帮助。
总结一下,我的投票是重构。
答案 1 :(得分:13)
我遇到了类似gui代码的问题,我最终做的是,
(defn- frame [args]
((resolve 'project.gui/frame) args))
这允许我在运行时解析调用,这是从帧中的菜单项调用的,因此我100%确定帧被定义,因为它是从帧本身调用的,请记住,resolve可能返回nil。
答案 2 :(得分:9)
我经常遇到同样的问题。尽管许多开发人员不愿意承认这一点,但这是该语言中一个严重的设计缺陷。循环依赖是真实对象的正常条件。没有心脏,身体就无法生存,没有身体,心脏就无法生存。
可能会在通话时解决,但它不会是最佳的。假设你有一个API,作为api的一部分是错误报告方法,但是api创建了一个有自己方法的对象,这些对象需要报告错误,你有循环依赖。错误检查和报告功能将经常被调用,因此在调用它们时解析不是一个选项。
在这种情况下,大多数情况下,解决方案是将没有依赖关系的代码移动到可以自由共享的独立(util)命名空间中。我还没遇到过用这种技术无法解决问题的情况。这使得维护完整,功能性的业务对象几乎不可能,但它似乎是唯一的选择。 Clojure还有很长的路要走,它是一种能够准确建模现实世界的成熟语言,直到那时以不合逻辑的方式划分代码是消除这些依赖关系的唯一方法。
如果Aa()依赖于Ba()和Bb()依赖于Ab(),唯一的解决方案是将Ba()移动到Ca()和/或Ab()移动到Cb(),即使C技术上没有&# 39; t存在于现实世界中。
答案 3 :(得分:1)
将所有内容移动到一个巨型源文件,以便您没有外部依赖项,或者重构。就个人而言,我会选择重构,但是当你真正了解它时,它就是关于美学的。有些人喜欢KLOCS和意大利面条代码,所以没有考虑品味。
答案 4 :(得分:0)
仔细考虑设计是件好事。循环依赖可能告诉我们,我们对重要的事情感到困惑。
这是我在一两个案例中用来解决循环依赖的技巧。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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学到了这个技巧。