18

我正在研究一些 Clojure 代码,这些代码在不同的命名空间之间具有一些循环依赖关系,并且我正在尝试找出解决它们的最佳方法。

  • 基本问题是我在其中一个文件中收到“No such var: namespace/functionname”错误
  • 我试图“声明”该函数,但随后它抱怨:“无法引用不存在的合格变量”
  • 我当然可以重构整个代码库,但是每次你有一个需要解决的依赖项时都这样做似乎不切实际......并且对于某些循环依赖项网络可能会变得非常丑陋
  • 我可以将一堆接口/协议/声明分离到一个单独的文件中,并让所有内容都引用它......但这似乎最终会变得混乱并破坏我拥有的当前良好的模块化结构以及相关的功能分组一起

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

4

5 回答 5

25

我记得很多关于 Clojure 中命名空间的讨论——在邮件列表和其他地方——我必须告诉你,共识(以及 AFAICT,Clojure 设计的当前方向)是循环依赖是设计的呼唤重构。解决方法有时可能是可能的,但很难看,可能会影响性能(如果你让事情不必要地“动态”),不能保证永远工作等等。

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

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

于 2010-06-21T13:11:13.773 回答
15

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

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

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

于 2010-06-21T12:52:23.020 回答
13

我经常遇到同样的问题。尽管许多开发人员不想承认,但这是该语言的严重设计缺陷。循环依赖是真实对象的正常情况。没有心脏,身体就无法生存,没有身体,心脏也无法生存。

在通话时解决可能是可能的,但它不是最佳的。以您有 API 的情况为例,因为该 api 的一部分是错误报告方法,但 api 创建了一个具有自己的方法的对象,这些对象将需要错误报告,并且您有循环依赖。错误检查和报告功能将经常被调用,因此在调用它们时解决不是一种选择。

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

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

于 2014-04-10T11:01:46.630 回答
0

要么将所有内容移动到一个巨大的源文件中,这样您就没有外部依赖项,要么进行重构。就我个人而言,我会选择重构,但当你真正开始着手时,一切都与美学有关。有些人喜欢 KLOCS 和意大利面条代码,所以没有考虑到味道。

于 2011-12-08T05:11:50.467 回答
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 在 Reagent 的代码中学到了这个技巧。

于 2017-09-10T23:22:03.053 回答