如果您根据类型或类进行调度,请考虑修改您的代码以使用Protocols。
更详细的答案:
如果还注册了精确的子类型,Clojure 的defmulti 不允许您分派到父 Java 类型。这是故意的(它在Liskov substitution principle 辩论中采取“最少惊喜”的一面)。由于 Y 已经注册了多方法,如果您的对象 isa? 恰好是 Y,那么您将点击 Y 的方法 - 即使它也是“X”。在 Java 中,类可以从多个接口继承,它们只能是完全一个类型。这就是你在这里遇到的问题。
根据documentation for multimethods
派生是由 Java 继承(对于类值)或使用 Clojure 的 ad hoc 层次系统的组合来确定的。层次系统支持名称(符号或关键字)之间的派生关系,以及类和名称之间的关系。派生函数创建这些关系,isa? 函数测试它们的存在。 注意isa? 不是instance?。
如果您查看 MultiFn 的源代码,您会发现 Clojure 总是使用给定的多方法调度值(在按类型调度时)最具体的 Java 类。
参见this line in Clojure 1.4.0 source for MultiFn,具体参见dominates的实现。
在 REPL:
user=> (defmulti foo #(class %))
user=> (defmethod foo java.util.RandomAccess [x] "RandomAccess")
user=> (defmethod foo java.util.Vector [x] "Vector")
user=> (defmethod foo java.util.Stack [x] "Stack")
好的,这按预期工作,prefer-method 不能覆盖类层次结构,因为isa? 首先检查 Java 类型。
user=> (prefer-method foo java.util.RandomAccess java.util.Stack)
user=> (foo (new java.util.Stack))
"Stack"
最后,在源代码中检查MultiFn 所有与调度值类型匹配的方法键(根据isa?)。如果找到多个匹配项,它会检查类型层次结构中的“支配”值。我们在这里看到Stack 支配RandomAccess
user=> (isa? java.util.Stack java.util.RandomAccess)
true
user=> (isa? java.util.RandomAccess java.util.Stack)
false
现在,如果我定义一个新方法 bar 如下:
user=> (defmulti bar #(class %))
user=> (defmethod bar Comparable [x] "Comparable")
user=> (defmethod bar java.io.Serializable [x] "Serializable")
由于模棱两可,我得到以下信息:
user=> (bar 1)
IllegalArgumentException Multiple methods in multimethod 'bar' match dispatch value: class java.lang.Long -> interface java.lang.Comparable and interface java.io.Serializable, and neither is preferred clojure.lang.MultiFn.findAndCacheBestMethod (MultiFn.java:136)
现在,我可以用prefer-method解决这个问题
user=> (prefer-method bar Comparable java.io.Serializable)
user=> (bar 1)
"Comparable"
但是,如果我为Long 注册一个新方法
user=> (defmethod bar Long [x] "Long")
user=> (bar 1)
"Long"
我无法回到Comparable,即使我使用prefer-method:
user=> (prefer-method bar Comparable Long)
user=> (bar 1)
"Long"
这似乎是你在这里遇到的。
请注意,您可以选择 remove-method - 但我认为与您设计的“黑客”相比,这是一个更重量级/更危险(猴子补丁?)的解决方案。