能否基于命名空间Map的命名空间实现Multimethod分发?无需hack方案
基于命名空间Map实现Multimethod分发的可靠方案
当然可以实现,而且完全有不依赖键前缀检查的正经方案——你说得没错,检查键前缀确实是hack,毕竟键的命名空间前缀可以被随意覆盖,就像你举的例子那样:
(:qux/bar #:qux{:bar :baz}) ; => :baz (:foo/bar #:qux{:foo/bar :baz}) ; => :baz (:qux/bar #:qux{:foo/bar :baz}) ; => nil
这种依赖键的做法太脆弱了,完全不靠谱。
正经实现思路:利用命名空间Map的固有命名空间关联
Clojure的命名空间Map(就是#:<ns>{}这种形式)本身是和特定命名空间绑定的,我们不需要去碰它的键,直接提取它关联的命名空间就能做可靠的Multimethod分发。
从Clojure 1.10开始,官方提供了两个专门的函数来处理命名空间Map:
namespace-map?:判断一个Map是不是命名空间Mapnamespace-map->ns:提取命名空间Map对应的命名空间对象
基于这两个函数,我们可以很干净地实现分发逻辑:
; 定义Multimethod,dispatch函数直接提取命名空间Map的关联命名空间 (defmulti handle-ns-map (fn [obj] (if (namespace-map? obj) (namespace-map->ns obj) :default))) ; 为qux命名空间的Map定义处理逻辑 (defmethod handle-ns-map 'qux [ns-map] (format "Processing qux namespace map: %s" ns-map)) ; 为foo命名空间的Map定义处理逻辑 (defmethod handle-ns-map 'foo [ns-map] (format "Processing foo namespace map: %s" ns-map)) ; 默认处理逻辑 (defmethod handle-ns-map :default [obj] (format "Not a recognized namespace map: %s" obj))
测试一下效果:
(handle-ns-map #:qux{:bar :baz}) ; => "Processing qux namespace map: #:qux{:bar :baz}" (handle-ns-map #:foo{:bar :baz}) ; => "Processing foo namespace map: #:foo{:bar :baz}" (handle-ns-map {:bar :baz}) ; => "Not a recognized namespace map: {:bar :baz}"
旧版本Clojure的兼容方案(如果必须用1.9及以下)
如果你的项目还在用更早的Clojure版本,没有namespace-map->ns,可以通过反射获取命名空间Map内部关联的命名空间(这是临时方案,建议尽量升级版本):
(defn ns-map->ns [ns-map] (when (instance? clojure.lang.NamespaceMap ns-map) (let [field (.getDeclaredField clojure.lang.NamespaceMap "ns")] (.setAccessible field true) (.get field ns-map)))) ; 然后把上面的dispatch函数换成这个就行 (defmulti handle-ns-map (fn [obj] (if-let [ns (ns-map->ns obj)] ns :default)))
这种方案完全不依赖键的内容,而是基于命名空间Map本身的固有属性做分发,根本不存在被覆盖的风险,完全符合你想要的非hack实现。
内容的提问来源于stack exchange,提问作者JAre
相关产品推荐
相关产品推荐

