Clojure:覆盖库中的一个函数

问题描述

这个问题是几天前 previous question I asked 的问题。评论之一是我应该放弃用于提取查询参数的 Ring 中间件并编写我自己的。我认为我会使用的一种替代方法是利用现有的方法来获得我想要的东西,并且我一直在研究 Ring 源代码。它几乎完全符合我的要求。如果我写出我是如何理解它的工作原理的:

  1. 中间件具有调用 params-request 的函数 wrap-params
  2. params-requestparams 映射添加request 映射,调用 assoc-query-params
  3. assoc-query-params 最终对传入的查询字符串调用 ring.util.codec/form-decode 以将其转换为地图
  4. form-decode 使用 assoc-conj 通过 reduce 将值合并到现有映射中
  5. assoc-conj 的文档字符串说

将键与映射中的值相关联。如果密钥已经存在于 映射,一个值向量与键相关联。

最后一个函数是我上一个问题中存在问题的函数(TL;DR:我希望地图的值在字符串或向量类中保持一致)。戴上面向对象的帽子后,我可以通过子类化和覆盖需要更改行为的方法来轻松解决此问题。但是,对于 Clojure,我看不到如何只替换一个函数而不必更改堆栈中的所有内容。这可能吗,是否容易,还是我应该以另一种方式这样做?如果涉及到它,我可以复制整个中间件库和编解码器库,但对我来说似乎有点重量级。

解决方法

我不同意不使用 Ring 的 param 中间件的建议。它为您提供有关传入参数的完美信息,因此如果您不喜欢字符串或列表的默认行为,您可以根据需要更改参数。

有很多方法可以做到这一点,但一种明显的方法是编写自己的中间件,并将其插入到 Ring 的 param 中间件和您的处理程序之间。

(defn wrap-take-last-param []
  (fn [handler]
    (fn [req]
      (handler 
        (update req :params 
                (fn [params]
                  (into {} 
                     (for [[k v] params]
                       [k (if (string? v) v,(last v)]))))))))

你可以写一些更有趣的东西,比如向函数添加一些参数,让你指定哪些参数只接收最后指定的参数,哪些参数总是作为列表接收。在这种情况下,您可能不想将它包装在整个处理程序中,而是单独包装每个路由以指定它们的预期参数。

,

虽然自定义中间件可能是解决此问题的最清晰方法,但不要忘记您始终可以使用 with-redefs 覆盖 任何 函数。例如:

SingleInstanceException()

虽然这主要在单元测试期间使用,但它是一个开放式逃生舱,可用于覆盖任何功能。

对于 Clojure,源代码中的 Var 与库中的 Var 之间没有区别(甚至 (ns tst.demo.core (:use tupelo.core tupelo.test)) (dotest (with-redefs [clojure.core/range (constantly "Bogus!")] (is= "Bogus!" (range 9)))) 本身,如示例所示)。