简短的回答是你处理这个问题的方式是完全正确的。如果您发现自己更新多方法以特别频繁地更改调度功能,(1)我认为这很不寻常:-),(2)您可以编写一套函数/宏来帮助重新加载。我画了两个未经测试的(!)宏来帮助下面的(2)。
为什么?
然而,首先简要讨论“为什么”。当前实现的多方法的调度函数查找不需要同步——调度 fn 存储在对象的final
字段中MultiFn
。这当然意味着您不能只更改给定多方法的调度函数——您必须重新创建多方法本身。正如您所指出的,这需要重新注册所有先前定义的方法,这很麻烦。
当前的行为使您可以重新加载带有defmethod
表单的命名空间,而不会丢失所有方法,但代价是替换实际的多方法会稍微麻烦一些,而这确实是您想要做的。
如果您真的想要,可以通过反射更改调度 fn,但这存在语义问题,特别是在多线程场景中(有关构造后对字段的反射更新的信息,请参阅Java 语言规范 17.5.3final
)。
黑客(非反射)
(2)的一种方法是在重新定义后使用宏自动重新添加方法(未经测试)
(defmacro redefmulti [multifn & defmulti-tail]
`(let [mt# (methods ~multifn)]
(ns-unmap (.ns (var ~multifn)) '~multifn)
(defmulti ~multifn ~@defmulti-tail)
(doseq [[dispval# meth#] mt#]
(.addMethod ~multifn dispval# meth#))))
另一种设计将使用一个名为 的宏with-method-reregistration
,它采用一个 seqable 的 multifn 名称和一个主体,并承诺在执行主体后重新注册方法;这是一个草图(同样,未经测试):
(defmacro with-method-reregistration [multifns & body]
`(let [mts# (doall (zipmap ~(map (partial list 'var) multifns)
(map methods ~multifns))))]
~@body
(doseq [[v# mt#] mts#
[dispval# meth#] mt#]
(.addMethod @v# dispval# meth#))))
你会用它来表示(with-method-reregistration [my-multi-1 my-multi-2] (require :reload 'ns1 ns2))
。不确定这是否值得失去清晰度。